1 /*
2   Copyright 2021 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 #include <changes_chroot.h>     /* PrepareChangesChroot() */
38 
39 #if !defined(__MINGW32__)
40 static bool MakeLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp, PromiseResult *result);
41 #endif
42 static char *AbsLinkPath(const char *from, const char *relto);
43 
44 /*****************************************************************************/
45 
46 #ifdef __MINGW32__
47 
VerifyLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)48 PromiseResult VerifyLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
49 {
50     RecordFailure(ctx, pp, attr, "Windows does not support symbolic links (at VerifyLink())");
51     return PROMISE_RESULT_FAIL;
52 }
53 
54 #else
55 
VerifyLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)56 PromiseResult VerifyLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
57 {
58     assert(attr != NULL);
59     /* VerifyHardLink() is a separate function */
60     assert(attr->link.link_type != FILE_LINK_TYPE_HARDLINK);
61 
62     char to[CF_BUFSIZE], linkbuf[CF_BUFSIZE], absto[CF_BUFSIZE];
63     struct stat sb;
64 
65     memset(to, 0, CF_BUFSIZE);
66 
67     const bool absolute_source = IsAbsoluteFileName(source);
68 
69     if ((!absolute_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 (!absolute_source)        /* 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     /* If making changes in a chroot, we need to get the link target into the
90      * chroot. */
91     if (ChrootChanges())
92     {
93         PrepareChangesChroot(absto);
94     }
95 
96     const char *changes_absto = absto;
97     if (ChrootChanges())
98     {
99         changes_absto = ToChangesChroot(absto);
100     }
101 
102     bool source_file_exists = true;
103 
104     if (stat(changes_absto, &sb) == -1)
105     {
106         Log(LOG_LEVEL_DEBUG, "No source file '%s'", absto);
107         source_file_exists = false;
108     }
109 
110     if ((!source_file_exists) && (attr->link.when_no_file != cfa_force) && (attr->link.when_no_file != cfa_delete))
111     {
112         Log(LOG_LEVEL_VERBOSE, "Source '%s' for linking is absent", absto);
113         RecordFailure(ctx, pp, attr, "Unable to create link '%s' -> '%s', no source", destination, to);
114         return PROMISE_RESULT_FAIL;
115     }
116 
117     const char *changes_destination = destination;
118     if (ChrootChanges())
119     {
120         changes_destination = ToChangesChroot(destination);
121     }
122 
123     PromiseResult result = PROMISE_RESULT_NOOP;
124     if ((!source_file_exists) && (attr->link.when_no_file == cfa_delete))
125     {
126         KillGhostLink(ctx, changes_destination, attr, pp, &result);
127         return result;
128     }
129 
130     memset(linkbuf, 0, CF_BUFSIZE);
131 
132     if (readlink(changes_destination, linkbuf, CF_BUFSIZE - 1) == -1)
133     {
134         if (!MakingChanges(ctx, pp, attr, &result, "create link '%s'", destination))
135         {
136             return result;
137         }
138 
139         bool dir_created = false;
140         if (MakeParentDirectoryForPromise(ctx, pp, attr, &result,
141                                           destination, attr->move_obstructions,
142                                           &dir_created))
143         {
144             if (dir_created)
145             {
146                 RecordChange(ctx, pp, attr, "Created parent directory for link '%s'", destination);
147                 result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
148             }
149             if (!MoveObstruction(ctx, destination, attr, pp, &result))
150             {
151                 RecordFailure(ctx, pp, attr,
152                               "Unable to create link '%s' -> '%s', failed to move obstruction",
153                               destination, to);
154                 result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
155                 return result;
156             }
157 
158             if (!MakeLink(ctx, destination, source, attr, pp, &result))
159             {
160                 RecordFailure(ctx, pp, attr, "Unable to create link '%s' -> '%s'", destination, to);
161                 result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
162             }
163         }
164         return result;
165     }
166     else
167     {
168         /* to == "./$source" */
169         bool link_correct = (StringEqual(linkbuf, source) || StringEqual(linkbuf, to));
170 
171         /* If making changes in chroot, the existing symlink can also be
172          * pointing to the changes chroot (if it's absolute). */
173         if (!link_correct && absolute_source && ChrootChanges())
174         {
175             link_correct = StringEqual(linkbuf, ToChangesChroot(source));
176         }
177         if (link_correct)
178         {
179             RecordNoChange(ctx, pp, attr, "Link '%s' points to '%s', promise kept", destination, source);
180             return PROMISE_RESULT_NOOP;
181         }
182         /* else */
183         if (attr->move_obstructions)
184         {
185             if (MakingChanges(ctx, pp, attr, &result, "remove incorrect link '%s'", destination))
186             {
187                 if (unlink(ToChangesPath(destination)) == -1)
188                 {
189                     RecordFailure(ctx, pp, attr, "Error removing link '%s' (points to '%s' not '%s')",
190                                   destination, linkbuf, to);
191                     return PROMISE_RESULT_FAIL;
192                 }
193                 RecordChange(ctx, pp, attr, "Overrode incorrect link '%s'", destination);
194                 result = PROMISE_RESULT_CHANGE;
195 
196                 MakeLink(ctx, destination, source, attr, pp, &result);
197                 return result;
198             }
199             else
200             {
201                 return result;
202             }
203         }
204         else
205         {
206             RecordFailure(ctx, pp, attr,
207                           "Link '%s' points to '%s' not '%s', but not moving obstructions",
208                           destination, linkbuf, to);
209             return PROMISE_RESULT_FAIL;
210         }
211     }
212 }
213 #endif /* !__MINGW32__ */
214 
215 /*****************************************************************************/
216 
217 #ifdef __MINGW32__
VerifyAbsoluteLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)218 PromiseResult VerifyAbsoluteLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
219 {
220     RecordFailure(ctx, pp, attr, "Windows does not support symbolic links (at VerifyAbsoluteLink())");
221     return PROMISE_RESULT_FAIL;
222 }
223 
224 #else
VerifyAbsoluteLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)225 PromiseResult VerifyAbsoluteLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
226 {
227     assert(attr != NULL);
228 
229     char absto[CF_BUFSIZE];
230     char expand[CF_BUFSIZE];
231     char linkto[CF_BUFSIZE];
232 
233     if (*source == '.')
234     {
235         strcpy(linkto, destination);
236         ChopLastNode(linkto);
237         JoinPaths(linkto, sizeof(linkto), source);
238     }
239     else
240     {
241         strcpy(linkto, source);
242     }
243 
244     CompressPath(absto, sizeof(absto), linkto);
245 
246     expand[0] = '\0';
247 
248     if (attr->link.when_no_file == cfa_force)
249     {
250         bool expanded;
251         struct stat sb;
252         if (ChrootChanges() && (lstat(ToChangesChroot(absto), &sb) != -1))
253         {
254             char chrooted_expand[sizeof(expand)];
255             chrooted_expand[0] = '\0';
256             expanded = ExpandLinks(chrooted_expand, ToChangesChroot(absto), 0, CF_MAXLINKLEVEL);
257             strlcpy(expand, ToNormalRoot(chrooted_expand), sizeof(expand));
258         }
259         else
260         {
261             expanded = ExpandLinks(expand, absto, 0, CF_MAXLINKLEVEL);
262         }
263         if (expanded)
264         {
265             Log(LOG_LEVEL_DEBUG, "ExpandLinks returned '%s'", expand);
266         }
267         else
268         {
269             RecordFailure(ctx, pp, attr, "Failed to expand absolute link to '%s'", source);
270             PromiseRef(LOG_LEVEL_ERR, pp);
271             return PROMISE_RESULT_FAIL;
272         }
273     }
274     else
275     {
276         strcpy(expand, absto);
277     }
278 
279     CompressPath(linkto, sizeof(linkto), expand);
280 
281     return VerifyLink(ctx, destination, linkto, attr, pp);
282 }
283 #endif  /* __MINGW32__ */
284 
285 /*****************************************************************************/
286 
VerifyRelativeLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)287 PromiseResult VerifyRelativeLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
288 {
289     char *sp, *commonto, *commonfrom;
290     char buff[CF_BUFSIZE], linkto[CF_BUFSIZE];
291     int levels = 0;
292 
293     if (*source == '.')
294     {
295         return VerifyLink(ctx, destination, source, attr, pp);
296     }
297 
298     if (!CompressPath(linkto, sizeof(linkto), source))
299     {
300         RecordInterruption(ctx, pp, attr, "Failed to link '%s' to '%s'", destination, source);
301         return PROMISE_RESULT_INTERRUPTED;
302     }
303 
304     commonto = linkto;
305     commonfrom = destination;
306 
307     if (strcmp(commonto, commonfrom) == 0)
308     {
309         RecordInterruption(ctx, pp, attr, "Failed to link '%s' to '%s', can't link file '%s' to itself",
310                            destination, source, commonto);
311         return PROMISE_RESULT_INTERRUPTED;
312     }
313 
314     while (*commonto == *commonfrom)
315     {
316         commonto++;
317         commonfrom++;
318     }
319 
320     while (!((IsAbsoluteFileName(commonto)) && (IsAbsoluteFileName(commonfrom))))
321     {
322         commonto--;
323         commonfrom--;
324     }
325 
326     commonto++;
327 
328     for (sp = commonfrom; *sp != '\0'; sp++)
329     {
330         if (IsFileSep(*sp))
331         {
332             levels++;
333         }
334     }
335 
336     memset(buff, 0, CF_BUFSIZE);
337 
338     strcat(buff, ".");
339     strcat(buff, FILE_SEPARATOR_STR);
340 
341     while (--levels > 0)
342     {
343         const char add[] = ".." FILE_SEPARATOR_STR;
344 
345         if (!PathAppend(buff, sizeof(buff), add, FILE_SEPARATOR))
346         {
347             RecordFailure(ctx, pp, attr,
348                           "Internal limit reached in VerifyRelativeLink(),"
349                           " path too long: '%s' + '%s'",
350                           buff, add);
351             return PROMISE_RESULT_FAIL;
352         }
353     }
354 
355     if (!PathAppend(buff, sizeof(buff), commonto, FILE_SEPARATOR))
356     {
357         RecordFailure(ctx, pp, attr,
358                       "Internal limit reached in VerifyRelativeLink() end,"
359                       " path too long: '%s' + '%s'",
360                       buff, commonto);
361         return PROMISE_RESULT_FAIL;
362     }
363 
364     return VerifyLink(ctx, destination, buff, attr, pp);
365 }
366 
367 /*****************************************************************************/
368 
VerifyHardLink(EvalContext * ctx,char * destination,const char * source,const Attributes * attr,const Promise * pp)369 PromiseResult VerifyHardLink(EvalContext *ctx, char *destination, const char *source, const Attributes *attr, const Promise *pp)
370 {
371     char to[CF_BUFSIZE], absto[CF_BUFSIZE];
372     struct stat ssb, dsb;
373 
374     memset(to, 0, CF_BUFSIZE);
375 
376     const bool absolute_source = IsAbsoluteFileName(source);
377 
378     if ((!absolute_source) && (*source != '.'))        /* links without a directory reference */
379     {
380         snprintf(to, CF_BUFSIZE - 1, ".%c%s", FILE_SEPARATOR, source);
381     }
382     else
383     {
384         strlcpy(to, source, CF_BUFSIZE);
385     }
386 
387     if (!absolute_source)        /* relative path, must still check if exists */
388     {
389         Log(LOG_LEVEL_DEBUG, "Relative link destination detected '%s'", to);
390         strcpy(absto, AbsLinkPath(destination, to));
391         Log(LOG_LEVEL_DEBUG, "Absolute path to relative link '%s', '%s'", absto, destination);
392     }
393     else
394     {
395         strcpy(absto, to);
396     }
397 
398     /* If making changes in a chroot, we need to get the link target into the
399      * chroot. */
400     if (ChrootChanges())
401     {
402         PrepareChangesChroot(absto);
403     }
404 
405     const char *changes_absto = absto;
406     if (ChrootChanges())
407     {
408         changes_absto = ToChangesChroot(absto);
409     }
410 
411     if (stat(changes_absto, &ssb) == -1)
412     {
413         Log(LOG_LEVEL_DEBUG, "No source file '%s'", absto);
414     }
415 
416     if (!S_ISREG(ssb.st_mode))
417     {
418         RecordFailure(ctx, pp, attr,
419                       "Source file '%s' is not a regular file, not appropriate to hard-link", to);
420         return PROMISE_RESULT_FAIL;
421     }
422 
423     Log(LOG_LEVEL_DEBUG, "Trying to hard link '%s' -> '%s'", destination, to);
424 
425     if (stat(ChrootChanges()? ToChangesChroot(destination) : destination, &dsb) == -1)
426     {
427         PromiseResult result = PROMISE_RESULT_NOOP;
428         MakeHardLink(ctx, destination, to, attr, pp, &result);
429         return result;
430     }
431 
432     /* both files exist, but are they the same file? POSIX says  */
433     /* the files could be on different devices, but unix doesn't */
434     /* allow this behaviour so the tests below are theoretical... */
435 
436     if ((dsb.st_ino != ssb.st_ino) && (dsb.st_dev != ssb.st_dev))
437     {
438         Log(LOG_LEVEL_VERBOSE,
439             "If this is POSIX, unable to determine if %s is hard link is correct"
440             " since it points to a different filesystem",
441             destination);
442 
443         if ((dsb.st_mode == ssb.st_mode) && (dsb.st_size == ssb.st_size))
444         {
445             RecordNoChange(ctx, pp, attr, "Hard link '%s' -> '%s' on different device appears okay",
446                            destination, to);
447             return PROMISE_RESULT_NOOP;
448         }
449     }
450 
451     if ((dsb.st_ino == ssb.st_ino) && (dsb.st_dev == ssb.st_dev))
452     {
453         RecordNoChange(ctx, pp, attr, "Hard link '%s' -> '%s' exists and is okay",
454                      destination, to);
455         return PROMISE_RESULT_NOOP;
456     }
457 
458     const char *chroot_msg = "";
459     if (ChrootChanges())
460     {
461         chroot_msg = " (but hardlinks are always replicated to the changes chroot)";
462     }
463     Log(LOG_LEVEL_INFO, "'%s' does not appear to be a hard link to '%s'%s", destination, to, chroot_msg);
464 
465     PromiseResult result = PROMISE_RESULT_NOOP;
466     if (!MakingChanges(ctx, pp, attr, &result,  "hard link '%s' -> '%s'", destination, to))
467     {
468         return result;
469     }
470 
471     if (!MoveObstruction(ctx, destination, attr, pp, &result))
472     {
473         RecordFailure(ctx, pp, attr,
474                       "Unable to create hard link '%s' -> '%s', failed to move obstruction",
475                       destination, to);
476         result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
477         return result;
478     }
479 
480     MakeHardLink(ctx, destination, to, attr, pp, &result);
481     return result;
482 }
483 
484 /*****************************************************************************/
485 /* Level                                                                     */
486 /*****************************************************************************/
487 
488 #ifdef __MINGW32__
489 
KillGhostLink(EvalContext * ctx,const char * name,const Attributes * attr,const Promise * pp,PromiseResult * result)490 bool KillGhostLink(EvalContext *ctx, const char *name, const Attributes *attr, const Promise *pp, PromiseResult *result)
491 {
492     RecordFailure(ctx, pp, attr, "Cannot remove dead link '%s' (Windows does not support symbolic links)", name);
493     PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
494     return false;
495 }
496 
497 #else                           /* !__MINGW32__ */
498 
KillGhostLink(EvalContext * ctx,const char * name,const Attributes * attr,const Promise * pp,PromiseResult * result)499 bool KillGhostLink(EvalContext *ctx, const char *name, const Attributes *attr, const Promise *pp,
500                    PromiseResult *result)
501 {
502     char linkbuf[CF_BUFSIZE], tmp[CF_BUFSIZE];
503     char linkpath[CF_BUFSIZE], *sp;
504     struct stat statbuf;
505 
506     memset(linkbuf, 0, CF_BUFSIZE);
507     memset(linkpath, 0, CF_BUFSIZE);
508 
509     const char *changes_name = name;
510     if (ChrootChanges())
511     {
512         changes_name = ToChangesChroot(name);
513     }
514 
515     if (readlink(changes_name, linkbuf, CF_BUFSIZE - 1) == -1)
516     {
517         Log(LOG_LEVEL_VERBOSE, "Can't read link '%s' while checking for deadlinks", name);
518         return true;            /* ignore */
519     }
520 
521     if (!IsAbsoluteFileName(linkbuf))
522     {
523         strcpy(linkpath, changes_name); /* Get path to link */
524 
525         for (sp = linkpath + strlen(linkpath); (*sp != FILE_SEPARATOR) && (sp >= linkpath); sp--)
526         {
527             *sp = '\0';
528         }
529     }
530 
531     strcat(linkpath, linkbuf);
532     CompressPath(tmp, sizeof(tmp), linkpath);
533 
534     if (stat(tmp, &statbuf) == -1)    /* link points nowhere */
535     {
536         if ((attr->link.when_no_file == cfa_delete) || (attr->recursion.rmdeadlinks))
537         {
538             Log(LOG_LEVEL_VERBOSE,
539                 "'%s' is a link which points to '%s', but the target doesn't seem to exist",
540                 name, linkbuf);
541 
542             if (MakingChanges(ctx, pp, attr, result, "remove dead link '%s'", name))
543             {
544                 unlink(changes_name);   /* May not work on a client-mounted system ! */
545                 RecordChange(ctx, pp, attr, "Removed dead link '%s'", name);
546                 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
547                 return true;
548             }
549             else
550             {
551                 return true;
552             }
553         }
554     }
555 
556     return false;
557 }
558 #endif /* !__MINGW32__ */
559 
560 /*****************************************************************************/
561 
562 #if !defined(__MINGW32__)
MakeLink(EvalContext * ctx,const char * from,const char * to,const Attributes * attr,const Promise * pp,PromiseResult * result)563 static bool MakeLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp,
564                      PromiseResult *result)
565 {
566     if (MakingChanges(ctx, pp, attr, result, "link files '%s' -> '%s'", from, to))
567     {
568         const char *changes_to = to;
569         char *chroot_to = NULL;
570         if (ChrootChanges())
571         {
572             /* Create a copy because the next call to ToChangesChroot() will
573              * overwrite the value. */
574             chroot_to = xstrdup(ToChangesChroot(to));
575             changes_to = chroot_to;
576         }
577         const char *changes_from = from;
578         if (ChrootChanges())
579         {
580             changes_from = ToChangesChroot(from);
581         }
582 
583         if (symlink(changes_to, changes_from) == -1)
584         {
585             RecordFailure(ctx, pp, attr, "Couldn't link '%s' to '%s'. (symlink: %s)",
586                           to, from, GetErrorStr());
587             *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
588             free(chroot_to);
589             return false;
590         }
591         else
592         {
593             RecordChange(ctx, pp, attr, "Linked files '%s' -> '%s'", from, to);
594             *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
595             free(chroot_to);
596             return true;
597         }
598     }
599     else
600     {
601         return false;
602     }
603 }
604 #endif /* !__MINGW32__ */
605 
606 /*****************************************************************************/
607 
608 #ifdef __MINGW32__
609 
MakeHardLink(EvalContext * ctx,const char * from,const char * to,const Attributes * attr,const Promise * pp,PromiseResult * result)610 bool MakeHardLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp,
611                   PromiseResult *result)
612 {                               // TODO: Implement ?
613     RecordFailure(ctx, pp, attr,
614                   "Couldn't hard link '%s' to '%s' (Hard links are not yet supported on Windows)",
615                   to, from);
616     *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
617     return false;
618 }
619 
620 #else                           /* !__MINGW32__ */
621 
MakeHardLink(EvalContext * ctx,const char * from,const char * to,const Attributes * attr,const Promise * pp,PromiseResult * result)622 bool MakeHardLink(EvalContext *ctx, const char *from, const char *to, const Attributes *attr, const Promise *pp,
623                   PromiseResult *result)
624 {
625     if (MakingChanges(ctx, pp, attr, result, "hard link files '%s' -> '%s'", from, to))
626     {
627         const char *changes_to = to;
628         char *chroot_to = NULL;
629         if (ChrootChanges())
630         {
631             /* Create a copy because the next call to ToChangesChroot() will
632              * overwrite the value. */
633             chroot_to = xstrdup(ToChangesChroot(to));
634             changes_to = chroot_to;
635         }
636         const char *changes_from = from;
637         if (ChrootChanges())
638         {
639             changes_from = ToChangesChroot(from);
640         }
641 
642         if (link(changes_to, changes_from) == -1)
643         {
644             RecordFailure(ctx, pp, attr, "Failed to hard link '%s' to '%s'. (link: %s)",
645                           to, from, GetErrorStr());
646             *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
647             return false;
648         }
649         else
650         {
651             RecordChange(ctx, pp, attr, "Hard linked file '%s' -> '%s'", from, to);
652             *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
653             return true;
654         }
655     }
656     else
657     {
658         return false;
659     }
660 }
661 
662 #endif /* !__MINGW32__ */
663 
664 /*********************************************************************/
665 
666 /* Expand a path contaning symbolic links, up to 4 levels  */
667 /* of symbolic links and then beam out in a hurry !        */
668 
669 #ifdef __MINGW32__
670 
ExpandLinks(char * dest,const char * from,int level,int max_level)671 bool ExpandLinks(char *dest, const char *from, int level, int max_level)
672 {
673     Log(LOG_LEVEL_ERR, "Windows does not support symbolic links (at ExpandLinks(%s,%s))", dest, from);
674     return false;
675 }
676 
677 #else                           /* !__MINGW32__ */
678 
ExpandLinks(char * dest,const char * from,int level,int max_level)679 bool ExpandLinks(char *dest, const char *from, int level, int max_level)
680 {
681     char buff[CF_BUFSIZE];
682     char node[CF_MAXLINKSIZE];
683     struct stat statbuf;
684     int lastnode = false;
685 
686     memset(dest, 0, CF_BUFSIZE);
687 
688     if (level >= CF_MAXLINKLEVEL)
689     {
690         Log(LOG_LEVEL_ERR, "Too many levels of symbolic links to evaluate absolute path");
691         return false;
692     }
693 
694     if (level >= max_level)
695     {
696         Log(LOG_LEVEL_DEBUG, "Reached maximum level of symbolic link resolution");
697         return true;
698     }
699 
700     const char *sp = from;
701 
702     while (*sp != '\0')
703     {
704         if (*sp == FILE_SEPARATOR)
705         {
706             sp++;
707             continue;
708         }
709 
710         sscanf(sp, "%[^/]", node);
711         sp += strlen(node);
712 
713         if (*sp == '\0')
714         {
715             lastnode = true;
716         }
717 
718         if (strcmp(node, ".") == 0)
719         {
720             continue;
721         }
722 
723         if (strcmp(node, "..") == 0)
724         {
725             strcat(dest, "/..");
726             continue;
727         }
728         else
729         {
730             strcat(dest, "/");
731         }
732 
733         strcat(dest, node);
734 
735         if (lstat(dest, &statbuf) == -1)        /* File doesn't exist so we can stop here */
736         {
737             Log(LOG_LEVEL_ERR, "Can't stat '%s' in ExpandLinks. (lstat: %s)", dest, GetErrorStr());
738             return false;
739         }
740 
741         if (S_ISLNK(statbuf.st_mode))
742         {
743             memset(buff, 0, CF_BUFSIZE);
744 
745             if (readlink(dest, buff, CF_BUFSIZE - 1) == -1)
746             {
747                 Log(LOG_LEVEL_ERR, "Expand links can't stat '%s'. (readlink: %s)", dest, GetErrorStr());
748                 return false;
749             }
750             else
751             {
752                 if (buff[0] == '.')
753                 {
754                     ChopLastNode(dest);
755                     AddSlash(dest);
756 
757                     /* TODO pass and use parameter dest_size. */
758                     size_t ret = strlcat(dest, buff, CF_BUFSIZE);
759                     if (ret >= CF_BUFSIZE)
760                     {
761                         Log(LOG_LEVEL_ERR,
762                             "Internal limit reached in ExpandLinks(),"
763                             " path too long: '%s' + '%s'",
764                             dest, buff);
765                         return false;
766                     }
767                 }
768                 else if (IsAbsoluteFileName(buff))
769                 {
770                     strcpy(dest, buff);
771                     DeleteSlash(dest);
772 
773                     if (strcmp(dest, from) == 0)
774                     {
775                         Log(LOG_LEVEL_DEBUG, "No links to be expanded");
776                         return true;
777                     }
778 
779                     if ((!lastnode) && (!ExpandLinks(buff, dest, level + 1, max_level)))
780                     {
781                         return false;
782                     }
783                 }
784                 else
785                 {
786                     ChopLastNode(dest);
787                     AddSlash(dest);
788 
789                     /* TODO use param dest_size. */
790                     size_t ret = strlcat(dest, buff, CF_BUFSIZE);
791                     if (ret >= CF_BUFSIZE)
792                     {
793                         Log(LOG_LEVEL_ERR,
794                             "Internal limit reached in ExpandLinks end,"
795                             " path too long: '%s' + '%s'", dest, buff);
796                         return false;
797                     }
798 
799                     DeleteSlash(dest);
800 
801                     if (strcmp(dest, from) == 0)
802                     {
803                         Log(LOG_LEVEL_DEBUG, "No links to be expanded");
804                         return true;
805                     }
806 
807                     memset(buff, 0, CF_BUFSIZE);
808 
809                     if ((!lastnode) && (!ExpandLinks(buff, dest, level + 1, max_level)))
810                     {
811                         return false;
812                     }
813                 }
814             }
815         }
816     }
817 
818     return true;
819 }
820 #endif /* !__MINGW32__ */
821 
822 /*********************************************************************/
823 
AbsLinkPath(const char * from,const char * relto)824 static char *AbsLinkPath(const char *from, const char *relto)
825 /* Take an abolute source and a relative destination object
826    and find the absolute name of the to object */
827 {
828     int pop = 1;
829     static char destination[CF_BUFSIZE]; /* GLOBAL_R, no need to initialize */
830 
831     if (IsAbsoluteFileName(relto))
832     {
833         ProgrammingError("Call to AbsLInkPath with absolute pathname");
834     }
835 
836     strcpy(destination, from);  /* reuse to save stack space */
837 
838     const char *sp = NULL;
839     for (sp = relto; *sp != '\0'; sp++)
840     {
841         if (strncmp(sp, "../", 3) == 0)
842         {
843             pop++;
844             sp += 2;
845             continue;
846         }
847 
848         if (strncmp(sp, "./", 2) == 0)
849         {
850             sp += 1;
851             continue;
852         }
853 
854         break;                  /* real link */
855     }
856 
857     while (pop > 0)
858     {
859         ChopLastNode(destination);
860         pop--;
861     }
862 
863     if (strlen(destination) == 0)
864     {
865         strcpy(destination, "/");
866     }
867     else
868     {
869         AddSlash(destination);
870     }
871 
872     strcat(destination, sp);
873     Log(LOG_LEVEL_DEBUG, "Reconstructed absolute linkname '%s'", destination);
874     return destination;
875 }
876