1 /*
2   Copyright 2020 Northern.tech AS
3 
4   This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6   This program is free software; you can redistribute it and/or modify it
7   under the terms of the GNU General Public License as published by the
8   Free Software Foundation; version 3.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
18 
19   To the extent this program is licensed as part of the Enterprise
20   versions of CFEngine, the applicable Commercial Open Source License
21   (COSL) may apply to this file if you as a licensee so wish it. See
22   included file COSL.txt.
23 */
24 
25 #include <files_links.h>
26 
27 #include <actuator.h>
28 #include <promises.h>
29 #include <files_names.h>
30 #include <files_interfaces.h>
31 #include <files_operators.h>
32 #include <files_lib.h>
33 #include <locks.h>
34 #include <string_lib.h>
35 #include <misc_lib.h>
36 #include <eval_context.h>
37 
38 #define CF_MAXLINKLEVEL 4
39 
40 #if !defined(__MINGW32__)
41 static bool MakeLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp, PromiseResult *result);
42 #endif
43 static char *AbsLinkPath(const char *from, const char *relto);
44 
EnforcePromise(enum cfopaction action)45 static bool EnforcePromise(enum cfopaction action)
46 {
47     return ((!DONTDO) && (action != cfa_warn));
48 }
49 
50 /*****************************************************************************/
51 
52 #ifdef __MINGW32__
53 
VerifyLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)54 PromiseResult VerifyLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
55 {
56     RecordFailure(ctx, pp, attr, "Windows does not support symbolic links (at VerifyLink())");
57     return PROMISE_RESULT_FAIL;
58 }
59 
60 #else
61 
VerifyLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)62 PromiseResult VerifyLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
63 {
64     char to[CF_BUFSIZE], linkbuf[CF_BUFSIZE], absto[CF_BUFSIZE];
65     struct stat sb;
66 
67     memset(to, 0, CF_BUFSIZE);
68 
69     if ((!IsAbsoluteFileName(source)) && (*source != '.'))        /* links without a directory reference */
70     {
71         snprintf(to, CF_BUFSIZE - 1, "./%s", source);
72     }
73     else
74     {
75         strlcpy(to, source, CF_BUFSIZE);
76     }
77 
78     if (!IsAbsoluteFileName(to))        /* relative path, must still check if exists */
79     {
80         Log(LOG_LEVEL_DEBUG, "Relative link destination detected '%s'", to);
81         strcpy(absto, AbsLinkPath(destination, to));
82         Log(LOG_LEVEL_DEBUG, "Absolute path to relative link '%s', '%s'", absto, destination);
83     }
84     else
85     {
86         strcpy(absto, to);
87     }
88 
89     bool source_file_exists = true;
90 
91     if (stat(absto, &sb) == -1)
92     {
93         Log(LOG_LEVEL_DEBUG, "No source file '%s'", absto);
94         source_file_exists = false;
95     }
96 
97     if ((!source_file_exists) && (attr->link.when_no_file != cfa_force) && (attr->link.when_no_file != cfa_delete))
98     {
99         Log(LOG_LEVEL_VERBOSE, "Source '%s' for linking is absent", absto);
100         RecordFailure(ctx, pp, attr, "Unable to create link '%s' -> '%s', no source", destination, to);
101         return PROMISE_RESULT_FAIL;
102     }
103 
104     if ((!source_file_exists) && (attr->link.when_no_file == cfa_delete))
105     {
106         PromiseResult result = PROMISE_RESULT_NOOP;
107         KillGhostLink(ctx, destination, attr, pp, &result);
108         return result;
109     }
110 
111     memset(linkbuf, 0, CF_BUFSIZE);
112 
113     if (readlink(destination, linkbuf, CF_BUFSIZE - 1) == -1)
114     {
115         if (!EnforcePromise(attr->transaction.action))
116         {
117             RecordWarning(ctx, pp, attr, "Link '%s' should be created", destination);
118             return PROMISE_RESULT_WARN;
119         }
120 
121         bool dir_created = false;
122         if (!MakeParentDirectory2(destination, attr->move_obstructions,
123                                   EnforcePromise(attr->transaction.action), &dir_created))
124         {
125             RecordFailure(ctx, pp, attr, "Unable to create parent directory of link '%s' -> '%s' (enforce %d)",
126                           destination, to, EnforcePromise(attr->transaction.action));
127             return PROMISE_RESULT_FAIL;
128         }
129         else
130         {
131             PromiseResult result = PROMISE_RESULT_NOOP;
132             if (dir_created)
133             {
134                 RecordChange(ctx, pp, attr, "Created parent directory for link '%s'", destination);
135                 result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
136             }
137             if (!MoveObstruction(ctx, destination, attr, pp, &result))
138             {
139                 RecordFailure(ctx, pp, attr,
140                               "Unable to create link '%s' -> '%s', failed to move obstruction",
141                               destination, to);
142                 result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
143                 return result;
144             }
145 
146             if (!MakeLink(ctx, destination, source, attr, pp, &result))
147             {
148                 RecordFailure(ctx, pp, attr, "Unable to create link '%s' -> '%s'", destination, to);
149                 result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
150             }
151 
152             return result;
153         }
154     }
155     else
156     {
157         int ok = false;
158 
159         if ((attr->link.link_type == FILE_LINK_TYPE_SYMLINK) && (strcmp(linkbuf, to) != 0) && (strcmp(linkbuf, source) != 0))
160         {
161             ok = true;
162         }
163         else if (strcmp(linkbuf, source) != 0)
164         {
165             ok = true;
166         }
167 
168         if (ok)
169         {
170             if (attr->move_obstructions)
171             {
172                 if (EnforcePromise(attr->transaction.action))
173                 {
174                     if (unlink(destination) == -1)
175                     {
176                         RecordFailure(ctx, pp, attr, "Error removing link '%s' (points to '%s' not '%s')",
177                                       destination, linkbuf, to);
178                         return PROMISE_RESULT_FAIL;
179                     }
180                     RecordChange(ctx, pp, attr, "Overrode incorrect link '%s'", destination);
181                     PromiseResult result = PROMISE_RESULT_CHANGE;
182 
183                     MakeLink(ctx, destination, source, attr, pp, &result);
184                     return result;
185                 }
186                 else
187                 {
188                     RecordWarning(ctx, pp, attr, "Should remove incorrect link '%s'", destination);
189                     return PROMISE_RESULT_WARN;
190                 }
191             }
192             else
193             {
194                 RecordFailure(ctx, pp, attr,
195                               "Link '%s' points to '%s' not '%s', but not moving obstructions",
196                               destination, linkbuf, to);
197                 return PROMISE_RESULT_FAIL;
198             }
199         }
200         else
201         {
202             RecordNoChange(ctx, pp, attr, "Link '%s' points to '%s', promise kept", destination, source);
203             return PROMISE_RESULT_NOOP;
204         }
205     }
206 }
207 #endif /* !__MINGW32__ */
208 
209 /*****************************************************************************/
210 
VerifyAbsoluteLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)211 PromiseResult VerifyAbsoluteLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
212 {
213     char absto[CF_BUFSIZE];
214     char expand[CF_BUFSIZE];
215     char linkto[CF_BUFSIZE];
216 
217     if (*source == '.')
218     {
219         strcpy(linkto, destination);
220         ChopLastNode(linkto);
221         AddSlash(linkto);
222         strcat(linkto, source);
223     }
224     else
225     {
226         strcpy(linkto, source);
227     }
228 
229     CompressPath(absto, sizeof(absto), linkto);
230 
231     expand[0] = '\0';
232 
233     if (attr->link.when_no_file == cfa_force)
234     {
235         if (!ExpandLinks(expand, absto, 0))     /* begin at level 1 and beam out at 15 */
236         {
237             RecordFailure(ctx, pp, attr, "Failed to expand absolute link to '%s'", source);
238             PromiseRef(LOG_LEVEL_ERR, pp);
239             return PROMISE_RESULT_FAIL;
240         }
241         else
242         {
243             Log(LOG_LEVEL_DEBUG, "ExpandLinks returned '%s'", expand);
244         }
245     }
246     else
247     {
248         strcpy(expand, absto);
249     }
250 
251     CompressPath(linkto, sizeof(linkto), expand);
252 
253     return VerifyLink(ctx, destination, linkto, attr, pp);
254 }
255 
256 /*****************************************************************************/
257 
VerifyRelativeLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)258 PromiseResult VerifyRelativeLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
259 {
260     char *sp, *commonto, *commonfrom;
261     char buff[CF_BUFSIZE], linkto[CF_BUFSIZE];
262     int levels = 0;
263 
264     if (*source == '.')
265     {
266         return VerifyLink(ctx, destination, source, attr, pp);
267     }
268 
269     if (!CompressPath(linkto, sizeof(linkto), source))
270     {
271         RecordInterruption(ctx, pp, attr, "Failed to link '%s' to '%s'", destination, source);
272         return PROMISE_RESULT_INTERRUPTED;
273     }
274 
275     commonto = linkto;
276     commonfrom = destination;
277 
278     if (strcmp(commonto, commonfrom) == 0)
279     {
280         RecordInterruption(ctx, pp, attr, "Failed to link '%s' to '%s', can't link file '%s' to itself",
281                            destination, source, commonto);
282         return PROMISE_RESULT_INTERRUPTED;
283     }
284 
285     while (*commonto == *commonfrom)
286     {
287         commonto++;
288         commonfrom++;
289     }
290 
291     while (!((IsAbsoluteFileName(commonto)) && (IsAbsoluteFileName(commonfrom))))
292     {
293         commonto--;
294         commonfrom--;
295     }
296 
297     commonto++;
298 
299     for (sp = commonfrom; *sp != '\0'; sp++)
300     {
301         if (IsFileSep(*sp))
302         {
303             levels++;
304         }
305     }
306 
307     memset(buff, 0, CF_BUFSIZE);
308 
309     strcat(buff, ".");
310     strcat(buff, FILE_SEPARATOR_STR);
311 
312     while (--levels > 0)
313     {
314         const char add[] = ".." FILE_SEPARATOR_STR;
315 
316         if (!PathAppend(buff, sizeof(buff), add, FILE_SEPARATOR))
317         {
318             RecordFailure(ctx, pp, attr,
319                           "Internal limit reached in VerifyRelativeLink(),"
320                           " path too long: '%s' + '%s'",
321                           buff, add);
322             return PROMISE_RESULT_FAIL;
323         }
324     }
325 
326     if (!PathAppend(buff, sizeof(buff), commonto, FILE_SEPARATOR))
327     {
328         RecordFailure(ctx, pp, attr,
329                       "Internal limit reached in VerifyRelativeLink() end,"
330                       " path too long: '%s' + '%s'",
331                       buff, commonto);
332         return PROMISE_RESULT_FAIL;
333     }
334 
335     return VerifyLink(ctx, destination, buff, attr, pp);
336 }
337 
338 /*****************************************************************************/
339 
VerifyHardLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)340 PromiseResult VerifyHardLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
341 {
342     char to[CF_BUFSIZE], absto[CF_BUFSIZE];
343     struct stat ssb, dsb;
344 
345     memset(to, 0, CF_BUFSIZE);
346 
347     if ((!IsAbsoluteFileName(source)) && (*source != '.'))        /* links without a directory reference */
348     {
349         snprintf(to, CF_BUFSIZE - 1, ".%c%s", FILE_SEPARATOR, source);
350     }
351     else
352     {
353         strlcpy(to, source, CF_BUFSIZE);
354     }
355 
356     if (!IsAbsoluteFileName(to))        /* relative path, must still check if exists */
357     {
358         Log(LOG_LEVEL_DEBUG, "Relative link destination detected '%s'", to);
359         strcpy(absto, AbsLinkPath(destination, to));
360         Log(LOG_LEVEL_DEBUG, "Absolute path to relative link '%s', destination '%s'", absto, destination);
361     }
362     else
363     {
364         strcpy(absto, to);
365     }
366 
367     if (stat(absto, &ssb) == -1)
368     {
369         RecordInterruption(ctx, pp, attr, "Source file '%s' doesn't exist", source);
370         return PROMISE_RESULT_INTERRUPTED;
371     }
372 
373     if (!S_ISREG(ssb.st_mode))
374     {
375         RecordFailure(ctx, pp, attr,
376                       "Source file '%s' is not a regular file, not appropriate to hard-link", to);
377         return PROMISE_RESULT_FAIL;
378     }
379 
380     Log(LOG_LEVEL_DEBUG, "Trying to hard link '%s' -> '%s'", destination, to);
381 
382     if (stat(destination, &dsb) == -1)
383     {
384         PromiseResult result = PROMISE_RESULT_NOOP;
385         MakeHardLink(ctx, destination, to, attr, pp, &result);
386         return result;
387     }
388 
389     /* both files exist, but are they the same file? POSIX says  */
390     /* the files could be on different devices, but unix doesn't */
391     /* allow this behaviour so the tests below are theoretical... */
392 
393     if ((dsb.st_ino != ssb.st_ino) && (dsb.st_dev != ssb.st_dev))
394     {
395         Log(LOG_LEVEL_VERBOSE,
396             "If this is POSIX, unable to determine if %s is hard link is correct"
397             " since it points to a different filesystem",
398             destination);
399 
400         if ((dsb.st_mode == ssb.st_mode) && (dsb.st_size == ssb.st_size))
401         {
402             RecordNoChange(ctx, pp, attr, "Hard link '%s' -> '%s' on different device appears okay",
403                            destination, to);
404             return PROMISE_RESULT_NOOP;
405         }
406     }
407 
408     if ((dsb.st_ino == ssb.st_ino) && (dsb.st_dev == ssb.st_dev))
409     {
410         RecordNoChange(ctx, pp, attr, "Hard link '%s' -> '%s' exists and is okay",
411                      destination, to);
412         return PROMISE_RESULT_NOOP;
413     }
414 
415     Log(LOG_LEVEL_INFO, "'%s' does not appear to be a hard link to '%s'", destination, to);
416 
417     if (!EnforcePromise(attr->transaction.action))
418     {
419         RecordWarning(ctx, pp, attr, "Hard link '%s' -> '%s' should be created", destination, to);
420         return PROMISE_RESULT_WARN;
421     }
422 
423     PromiseResult result = PROMISE_RESULT_NOOP;
424     if (!MoveObstruction(ctx, destination, attr, pp, &result))
425     {
426         RecordFailure(ctx, pp, attr,
427                       "Unable to create hard link '%s' -> '%s', failed to move obstruction",
428                       destination, to);
429         result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
430         return result;
431     }
432 
433     MakeHardLink(ctx, destination, to, attr, pp, &result);
434     return result;
435 }
436 
437 /*****************************************************************************/
438 /* Level                                                                     */
439 /*****************************************************************************/
440 
441 #ifdef __MINGW32__
442 
KillGhostLink(EvalContext * ctx,const char * name,const Attributes * attr,const Promise * pp,PromiseResult * result)443 bool KillGhostLink(EvalContext *ctx, const char *name, const Attributes *attr, const Promise *pp, PromiseResult *result)
444 {
445     RecordFailure(ctx, pp, attr, "Cannot remove dead link '%s' (Windows does not support symbolic links)", name);
446     PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
447     return false;
448 }
449 
450 #else                           /* !__MINGW32__ */
451 
KillGhostLink(EvalContext * ctx,const char * name,const Attributes * attr,const Promise * pp,PromiseResult * result)452 bool KillGhostLink(EvalContext *ctx, const char *name, const Attributes *attr, const Promise *pp,
453                    PromiseResult *result)
454 {
455     char linkbuf[CF_BUFSIZE], tmp[CF_BUFSIZE];
456     char linkpath[CF_BUFSIZE], *sp;
457     struct stat statbuf;
458 
459     memset(linkbuf, 0, CF_BUFSIZE);
460     memset(linkpath, 0, CF_BUFSIZE);
461 
462     if (readlink(name, linkbuf, CF_BUFSIZE - 1) == -1)
463     {
464         Log(LOG_LEVEL_VERBOSE, "Can't read link '%s' while checking for deadlinks", name);
465         return true;            /* ignore */
466     }
467 
468     if (!IsAbsoluteFileName(linkbuf))
469     {
470         strcpy(linkpath, name); /* Get path to link */
471 
472         for (sp = linkpath + strlen(linkpath); (*sp != FILE_SEPARATOR) && (sp >= linkpath); sp--)
473         {
474             *sp = '\0';
475         }
476     }
477 
478     strcat(linkpath, linkbuf);
479     CompressPath(tmp, sizeof(tmp), linkpath);
480 
481     if (stat(tmp, &statbuf) == -1)    /* link points nowhere */
482     {
483         if ((attr->link.when_no_file == cfa_delete) || (attr->recursion.rmdeadlinks))
484         {
485             Log(LOG_LEVEL_VERBOSE, "'%s' is a link which points to '%s', but that file doesn't seem to exist", name,
486                   linkbuf);
487 
488             if (DONTDO)
489             {
490                 RecordWarning(ctx, pp, attr, "Dead link '%s' should be removed", name);
491                 *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
492                 return true;
493             }
494             else
495             {
496                 unlink(name);   /* May not work on a client-mounted system ! */
497                 RecordChange(ctx, pp, attr, "Removing ghost '%s', reference to something that is not there",
498                              name);
499                 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
500                 return true;
501             }
502         }
503     }
504 
505     return false;
506 }
507 #endif /* !__MINGW32__ */
508 
509 /*****************************************************************************/
510 
511 #if !defined(__MINGW32__)
MakeLink(EvalContext * ctx,const char * from,const char * to,const Attributes * attr,const Promise * pp,PromiseResult * result)512 static bool MakeLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp,
513                      PromiseResult *result)
514 {
515     if (DONTDO || (attr->transaction.action == cfa_warn))
516     {
517         RecordWarning(ctx, pp, attr, "Should link files '%s' -> '%s'", from, to);
518         *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
519         return false;
520     }
521     else
522     {
523         if (symlink(to, from) == -1)
524         {
525             RecordFailure(ctx, pp, attr, "Couldn't link '%s' to '%s'. (symlink: %s)",
526                           to, from, GetErrorStr());
527             *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
528             return false;
529         }
530         else
531         {
532             RecordChange(ctx, pp, attr, "Linked files '%s' -> '%s'", from, to);
533             *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
534             return true;
535         }
536     }
537 }
538 #endif /* !__MINGW32__ */
539 
540 /*****************************************************************************/
541 
542 #ifdef __MINGW32__
543 
MakeHardLink(EvalContext * ctx,const char * from,const char * to,const Attributes * attr,const Promise * pp,PromiseResult * result)544 bool MakeHardLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp,
545                   PromiseResult *result)
546 {                               // TODO: Implement ?
547     RecordFailure(ctx, pp, attr,
548                   "Couldn't hard link '%s' to '%s' (Hard links are not yet supported on Windows)",
549                   to, from);
550     *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
551     return false;
552 }
553 
554 #else                           /* !__MINGW32__ */
555 
MakeHardLink(EvalContext * ctx,const char * from,const char * to,const Attributes * attr,const Promise * pp,PromiseResult * result)556 bool MakeHardLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp,
557                   PromiseResult *result)
558 {
559     if (DONTDO)
560     {
561         RecordWarning(ctx, pp, attr, "Should hard link files '%s' -> '%s'", from, to);
562         *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
563         return false;
564     }
565     else
566     {
567         if (link(to, from) == -1)
568         {
569             RecordFailure(ctx, pp, attr, "Failed to hard link '%s' to '%s'. (link: %s)",
570                           to, from, GetErrorStr());
571             *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
572             return false;
573         }
574         else
575         {
576             RecordChange(ctx, pp, attr, "Hard linked file '%s' -> '%s'", from, to);
577             *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
578             return true;
579         }
580     }
581 }
582 
583 #endif /* !__MINGW32__ */
584 
585 /*********************************************************************/
586 
587 /* Expand a path contaning symbolic links, up to 4 levels  */
588 /* of symbolic links and then beam out in a hurry !        */
589 
590 #ifdef __MINGW32__
591 
ExpandLinks(char * dest,const char * from,int level)592 bool ExpandLinks(char *dest, const char *from, int level)
593 {
594     Log(LOG_LEVEL_ERR, "Windows does not support symbolic links (at ExpandLinks(%s,%s))", dest, from);
595     return false;
596 }
597 
598 #else                           /* !__MINGW32__ */
599 
ExpandLinks(char * dest,const char * from,int level)600 bool ExpandLinks(char *dest, const char *from, int level)
601 {
602     char buff[CF_BUFSIZE];
603     char node[CF_MAXLINKSIZE];
604     struct stat statbuf;
605     int lastnode = false;
606 
607     memset(dest, 0, CF_BUFSIZE);
608 
609     if (level >= CF_MAXLINKLEVEL)
610     {
611         Log(LOG_LEVEL_ERR, "Too many levels of symbolic links to evaluate absolute path");
612         return false;
613     }
614 
615     const char *sp = from;
616 
617     while (*sp != '\0')
618     {
619         if (*sp == FILE_SEPARATOR)
620         {
621             sp++;
622             continue;
623         }
624 
625         sscanf(sp, "%[^/]", node);
626         sp += strlen(node);
627 
628         if (*sp == '\0')
629         {
630             lastnode = true;
631         }
632 
633         if (strcmp(node, ".") == 0)
634         {
635             continue;
636         }
637 
638         if (strcmp(node, "..") == 0)
639         {
640             strcat(dest, "/..");
641             continue;
642         }
643         else
644         {
645             strcat(dest, "/");
646         }
647 
648         strcat(dest, node);
649 
650         if (lstat(dest, &statbuf) == -1)        /* File doesn't exist so we can stop here */
651         {
652             Log(LOG_LEVEL_ERR, "Can't stat '%s' in ExpandLinks. (lstat: %s)", dest, GetErrorStr());
653             return false;
654         }
655 
656         if (S_ISLNK(statbuf.st_mode))
657         {
658             memset(buff, 0, CF_BUFSIZE);
659 
660             if (readlink(dest, buff, CF_BUFSIZE - 1) == -1)
661             {
662                 Log(LOG_LEVEL_ERR, "Expand links can't stat '%s'. (readlink: %s)", dest, GetErrorStr());
663                 return false;
664             }
665             else
666             {
667                 if (buff[0] == '.')
668                 {
669                     ChopLastNode(dest);
670                     AddSlash(dest);
671 
672                     /* TODO pass and use parameter dest_size. */
673                     size_t ret = strlcat(dest, buff, CF_BUFSIZE);
674                     if (ret >= CF_BUFSIZE)
675                     {
676                         Log(LOG_LEVEL_ERR,
677                             "Internal limit reached in ExpandLinks(),"
678                             " path too long: '%s' + '%s'",
679                             dest, buff);
680                         return false;
681                     }
682                 }
683                 else if (IsAbsoluteFileName(buff))
684                 {
685                     strcpy(dest, buff);
686                     DeleteSlash(dest);
687 
688                     if (strcmp(dest, from) == 0)
689                     {
690                         Log(LOG_LEVEL_DEBUG, "No links to be expanded");
691                         return true;
692                     }
693 
694                     if ((!lastnode) && (!ExpandLinks(buff, dest, level + 1)))
695                     {
696                         return false;
697                     }
698                 }
699                 else
700                 {
701                     ChopLastNode(dest);
702                     AddSlash(dest);
703 
704                     /* TODO use param dest_size. */
705                     size_t ret = strlcat(dest, buff, CF_BUFSIZE);
706                     if (ret >= CF_BUFSIZE)
707                     {
708                         Log(LOG_LEVEL_ERR,
709                             "Internal limit reached in ExpandLinks end,"
710                             " path too long: '%s' + '%s'", dest, buff);
711                         return false;
712                     }
713 
714                     DeleteSlash(dest);
715 
716                     if (strcmp(dest, from) == 0)
717                     {
718                         Log(LOG_LEVEL_DEBUG, "No links to be expanded");
719                         return true;
720                     }
721 
722                     memset(buff, 0, CF_BUFSIZE);
723 
724                     if ((!lastnode) && (!ExpandLinks(buff, dest, level + 1)))
725                     {
726                         return false;
727                     }
728                 }
729             }
730         }
731     }
732 
733     return true;
734 }
735 #endif /* !__MINGW32__ */
736 
737 /*********************************************************************/
738 
AbsLinkPath(const char * from,const char * relto)739 static char *AbsLinkPath(const char *from, const char *relto)
740 /* Take an abolute source and a relative destination object
741    and find the absolute name of the to object */
742 {
743     int pop = 1;
744     static char destination[CF_BUFSIZE]; /* GLOBAL_R, no need to initialize */
745 
746     if (IsAbsoluteFileName(relto))
747     {
748         ProgrammingError("Call to AbsLInkPath with absolute pathname");
749     }
750 
751     strcpy(destination, from);  /* reuse to save stack space */
752 
753     const char *sp = NULL;
754     for (sp = relto; *sp != '\0'; sp++)
755     {
756         if (strncmp(sp, "../", 3) == 0)
757         {
758             pop++;
759             sp += 2;
760             continue;
761         }
762 
763         if (strncmp(sp, "./", 2) == 0)
764         {
765             sp += 1;
766             continue;
767         }
768 
769         break;                  /* real link */
770     }
771 
772     while (pop > 0)
773     {
774         ChopLastNode(destination);
775         pop--;
776     }
777 
778     if (strlen(destination) == 0)
779     {
780         strcpy(destination, "/");
781     }
782     else
783     {
784         AddSlash(destination);
785     }
786 
787     strcat(destination, sp);
788     Log(LOG_LEVEL_DEBUG, "Reconstructed absolute linkname '%s'", destination);
789     return destination;
790 }
791