1 /* $NetBSD: cmds.c,v 1.29 2009/03/02 03:47:44 lukem Exp $ */
4 * Copyright (c) 1999-2009 The NetBSD Foundation, Inc.
7 * This code is derived from software contributed to The NetBSD Foundation
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
33 * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994
34 * The Regents of the University of California. All rights reserved.
36 * Redistribution and use in source and binary forms, with or without
37 * modification, are permitted provided that the following conditions
39 * 1. Redistributions of source code must retain the above copyright
40 * notice, this list of conditions and the following disclaimer.
41 * 2. Redistributions in binary form must reproduce the above copyright
42 * notice, this list of conditions and the following disclaimer in the
43 * documentation and/or other materials provided with the distribution.
44 * 3. Neither the name of the University nor the names of its contributors
45 * may be used to endorse or promote products derived from this software
46 * without specific prior written permission.
48 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
49 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
50 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
51 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
52 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
53 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
54 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
55 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
56 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
57 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
62 * Copyright (C) 1997 and 1998 WIDE Project.
63 * All rights reserved.
65 * Redistribution and use in source and binary forms, with or without
66 * modification, are permitted provided that the following conditions
68 * 1. Redistributions of source code must retain the above copyright
69 * notice, this list of conditions and the following disclaimer.
70 * 2. Redistributions in binary form must reproduce the above copyright
71 * notice, this list of conditions and the following disclaimer in the
72 * documentation and/or other materials provided with the distribution.
73 * 3. Neither the name of the project nor the names of its contributors
74 * may be used to endorse or promote products derived from this software
75 * without specific prior written permission.
77 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
78 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
79 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
80 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
81 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
82 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
83 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
84 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
85 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
86 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
91 #include <sys/cdefs.h>
93 __RCSID("$NetBSD: cmds.c,v 1.29 2009/03/02 03:47:44 lukem Exp $");
96 #include <sys/param.h>
111 #include <krb5/krb5.h>
117 FE_MLSD
= 1<<0, /* if op is MLSD (MLST otherwise ) */
118 FE_ISCURDIR
= 1<<1, /* if name is the current directory */
122 const char *path
; /* full pathname */
123 const char *display
; /* name to display */
124 struct stat
*stat
; /* stat of path */
125 struct stat
*pdirstat
; /* stat of path's parent dir */
126 factflag_t flags
; /* flags */
129 static void ack(const char *);
130 static void base64_encode(const char *, size_t, char *, int);
131 static void fact_type(const char *, FILE *, factelem
*);
132 static void fact_size(const char *, FILE *, factelem
*);
133 static void fact_modify(const char *, FILE *, factelem
*);
134 static void fact_perm(const char *, FILE *, factelem
*);
135 static void fact_unique(const char *, FILE *, factelem
*);
136 static int matchgroup(gid_t
);
137 static void mlsname(FILE *, factelem
*);
138 static void replydirname(const char *, const char *);
141 const char *name
; /* name of fact */
142 int enabled
; /* if fact is enabled */
143 void (*display
)(const char *, FILE *, factelem
*);
144 /* function to display fact */
147 struct ftpfact facttab
[] = {
148 { "Type", 1, fact_type
},
150 { "Size", 1, fact_size
},
151 { "Modify", 1, fact_modify
},
152 { "Perm", 1, fact_perm
},
153 { "Unique", 1, fact_unique
},
160 #define FACTTABSIZE (sizeof(facttab) / sizeof(struct ftpfact))
162 static char cached_path
[MAXPATHLEN
+ 1] = "/";
163 static void discover_path(char *, const char *);
166 cwd(const char *path
)
170 perror_reply(550, path
);
172 show_chdir_messages(250);
174 if (getcwd(cached_path
, MAXPATHLEN
) == NULL
) {
175 discover_path(cached_path
, path
);
181 delete(const char *name
)
185 if (remove(name
) < 0) {
187 perror_reply(550, name
);
190 logxfer("delete", -1, name
, NULL
, NULL
, p
);
198 reply(-211, "Features supported");
199 cprintf(stdout
, " MDTM\r\n");
200 cprintf(stdout
, " MLST ");
201 for (i
= 0; i
< FACTTABSIZE
; i
++)
202 cprintf(stdout
, "%s%s;", facttab
[i
].name
,
203 facttab
[i
].enabled
? "*" : "");
204 cprintf(stdout
, "\r\n");
205 cprintf(stdout
, " REST STREAM\r\n");
206 cprintf(stdout
, " SIZE\r\n");
207 cprintf(stdout
, " TVFS\r\n");
212 makedir(const char *name
)
216 if (mkdir(name
, 0777) < 0) {
218 perror_reply(550, name
);
220 replydirname(name
, "directory created.");
221 logxfer("mkdir", -1, name
, NULL
, NULL
, p
);
225 mlsd(const char *path
)
228 struct stat sb
, pdirstat
;
232 char name
[MAXPATHLEN
];
235 hastypefact
= facttab
[FACT_TYPE
].enabled
;
238 if (stat(path
, &pdirstat
) == -1) {
240 perror_reply(550, path
);
243 if (! S_ISDIR(pdirstat
.st_mode
)) {
245 perror_reply(501, path
);
248 if ((dirp
= opendir(path
)) == NULL
)
251 dout
= dataconn("MLSD", (off_t
)-1, "w");
255 memset(&f
, 0, sizeof(f
));
258 while ((dp
= readdir(dirp
)) != NULL
) {
259 snprintf(name
, sizeof(name
), "%s/%s", path
, dp
->d_name
);
260 if (ISDOTDIR(dp
->d_name
)) { /* special case curdir: */
263 f
.pdirstat
= NULL
; /* require stat of parent */
264 f
.display
= path
; /* set name to real name */
265 f
.flags
|= FE_ISCURDIR
; /* flag name is curdir */
267 if (ISDOTDOTDIR(dp
->d_name
)) {
272 f
.pdirstat
= &pdirstat
; /* cache parent stat */
273 f
.display
= dp
->d_name
;
274 f
.flags
&= ~FE_ISCURDIR
;
276 if (stat(name
, &sb
) == -1)
281 (void)closedir(dirp
);
283 if (ferror(dout
) != 0)
284 perror_reply(550, "Data connection");
286 reply(226, "MLSD complete.");
293 mlst(const char *path
)
300 if (stat(path
, &sb
) == -1) {
301 perror_reply(550, path
);
304 reply(-250, "MLST %s", path
);
305 memset(&f
, 0, sizeof(f
));
317 opts(const char *command
)
322 if ((ep
= strchr(command
, ' ')) != NULL
)
324 c
= lookup(cmdtab
, command
);
326 reply(502, "Unknown command '%s'.", command
);
329 if (! CMD_IMPLEMENTED(c
)) {
330 reply(502, "%s command not implemented.", c
->name
);
333 if (! CMD_HAS_OPTIONS(c
)) {
334 reply(501, "%s command does not support persistent options.",
339 /* special case: MLST */
340 if (strcasecmp(command
, "MLST") == 0) {
341 int enabled
[FACTTABSIZE
];
346 for (i
= 0; i
< sizeof(enabled
) / sizeof(int); i
++)
348 if (ep
== NULL
|| *ep
== '\0')
349 goto displaymlstopts
;
351 /* don't like spaces, and need trailing ; */
353 if (strchr(ep
, ' ') != NULL
|| ep
[len
- 1] != ';') {
355 reply(501, "Invalid MLST options");
359 while ((p
= strsep(&ep
, ";")) != NULL
) {
362 for (i
= 0; i
< FACTTABSIZE
; i
++)
363 if (strcasecmp(p
, facttab
[i
].name
) == 0) {
370 for (i
= 0; i
< FACTTABSIZE
; i
++)
371 facttab
[i
].enabled
= enabled
[i
];
372 cprintf(stdout
, "200 MLST OPTS");
373 for (i
= onedone
= 0; i
< FACTTABSIZE
; i
++) {
374 if (facttab
[i
].enabled
) {
375 cprintf(stdout
, "%s%s;", onedone
? "" : " ",
380 cprintf(stdout
, "\r\n");
386 if (ep
!= NULL
&& *ep
!= '\0')
387 REASSIGN(c
->options
, ftpd_strdup(ep
));
388 if (c
->options
!= NULL
)
389 reply(200, "Options for %s are '%s'.", c
->name
,
392 reply(200, "No options defined for %s.", c
->name
);
398 char path
[MAXPATHLEN
];
400 if (getcwd(path
, sizeof(path
) - 1) == NULL
) {
401 if (chdir(cached_path
) < 0) {
402 reply(550, "Can't get the current directory: %s.",
406 (void)strlcpy(path
, cached_path
, MAXPATHLEN
);
408 replydirname(path
, "is the current directory.");
412 removedir(const char *name
)
416 if (rmdir(name
) < 0) {
418 perror_reply(550, name
);
421 logxfer("rmdir", -1, name
, NULL
, NULL
, p
);
425 renamefrom(const char *name
)
429 if (stat(name
, &st
) < 0) {
430 perror_reply(550, name
);
433 reply(350, "File exists, ready for destination name");
434 return (ftpd_strdup(name
));
438 renamecmd(const char *from
, const char *to
)
442 if (rename(from
, to
) < 0) {
444 perror_reply(550, "rename");
447 logxfer("rename", -1, from
, to
, NULL
, p
);
451 sizecmd(const char *filename
)
458 if (stat(filename
, &stbuf
) < 0 || !S_ISREG(stbuf
.st_mode
))
459 reply(550, "%s: not a plain file.", filename
);
461 reply(213, ULLF
, (ULLT
)stbuf
.st_size
);
470 fin
= fopen(filename
, "r");
472 perror_reply(550, filename
);
475 if (fstat(fileno(fin
), &stbuf
) < 0 || !S_ISREG(stbuf
.st_mode
)) {
476 reply(550, "%s: not a plain file.", filename
);
480 if (stbuf
.st_size
> 10240) {
481 reply(550, "%s: file too large for SIZE.", filename
);
487 while((c
= getc(fin
)) != EOF
) {
488 if (c
== '\n') /* will get expanded to \r\n */
494 reply(213, LLF
, (LLT
)count
);
498 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type
]);
503 statfilecmd(const char *filename
)
508 const char *argv
[] = { INTERNAL_LS
, "-lgA", "", NULL
};
511 fin
= ftpd_popen(argv
, "r", STDOUT_FILENO
);
512 reply(-211, "status of %s:", filename
);
513 /* XXX: use fgetln() or fparseln() here? */
515 while ((c
= getc(fin
)) != EOF
) {
518 perror_reply(421, "control connection");
519 (void) ftpd_pclose(fin
);
524 perror_reply(551, filename
);
525 (void) ftpd_pclose(fin
);
530 if (atstart
&& isdigit(c
))
533 atstart
= (c
== '\n');
535 (void) ftpd_pclose(fin
);
536 reply(211, "End of Status");
545 reply(250, "%s command successful.", s
);
549 * Encode len bytes starting at clear using base64 encoding into encoded,
550 * which should be at least ((len + 2) * 4 / 3 + 1) in size.
551 * If nulterm is non-zero, terminate with \0 otherwise pad to 3 byte boundary
555 base64_encode(const char *clear
, size_t len
, char *encoded
, int nulterm
)
557 static const char base64
[] =
558 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
563 /* determine whether to pad with '=' or NUL terminate */
564 termchar
= nulterm
? '\0' : '=';
567 /* convert all but last 2 bytes */
568 for (i
= len
; i
> 2; i
-= 3, c
+= 3) {
569 *e
++ = base64
[(c
[0] >> 2) & 0x3f];
570 *e
++ = base64
[((c
[0] << 4) & 0x30) | ((c
[1] >> 4) & 0x0f)];
571 *e
++ = base64
[((c
[1] << 2) & 0x3c) | ((c
[2] >> 6) & 0x03)];
572 *e
++ = base64
[(c
[2]) & 0x3f];
574 /* handle slop at end */
576 *e
++ = base64
[(c
[0] >> 2) & 0x3f];
577 *e
++ = base64
[((c
[0] << 4) & 0x30) |
578 (i
> 1 ? ((c
[1] >> 4) & 0x0f) : 0)];
579 *e
++ = (i
> 1) ? base64
[(c
[1] << 2) & 0x3c] : termchar
;
586 fact_modify(const char *fact
, FILE *fd
, factelem
*fe
)
590 t
= gmtime(&(fe
->stat
->st_mtime
));
591 cprintf(fd
, "%s=%04d%02d%02d%02d%02d%02d;", fact
,
592 TM_YEAR_BASE
+ t
->tm_year
,
593 t
->tm_mon
+1, t
->tm_mday
,
594 t
->tm_hour
, t
->tm_min
, t
->tm_sec
);
598 fact_perm(const char *fact
, FILE *fd
, factelem
*fe
)
600 int rok
, wok
, xok
, pdirwok
;
603 if (fe
->stat
->st_uid
== geteuid()) {
604 rok
= ((fe
->stat
->st_mode
& S_IRUSR
) != 0);
605 wok
= ((fe
->stat
->st_mode
& S_IWUSR
) != 0);
606 xok
= ((fe
->stat
->st_mode
& S_IXUSR
) != 0);
607 } else if (matchgroup(fe
->stat
->st_gid
)) {
608 rok
= ((fe
->stat
->st_mode
& S_IRGRP
) != 0);
609 wok
= ((fe
->stat
->st_mode
& S_IWGRP
) != 0);
610 xok
= ((fe
->stat
->st_mode
& S_IXGRP
) != 0);
612 rok
= ((fe
->stat
->st_mode
& S_IROTH
) != 0);
613 wok
= ((fe
->stat
->st_mode
& S_IWOTH
) != 0);
614 xok
= ((fe
->stat
->st_mode
& S_IXOTH
) != 0);
617 cprintf(fd
, "%s=", fact
);
620 * if parent info not provided, look it up, but
621 * only if the current class has modify rights,
622 * since we only need this info in such a case.
625 if (pdir
== NULL
&& CURCLASS_FLAGS_ISSET(modify
)) {
627 char realdir
[MAXPATHLEN
], *p
;
630 len
= strlcpy(realdir
, fe
->path
, sizeof(realdir
));
631 if (len
< sizeof(realdir
) - 4) {
632 if (S_ISDIR(fe
->stat
->st_mode
))
633 strlcat(realdir
, "/..", sizeof(realdir
));
635 /* if has a /, move back to it */
636 /* otherwise use '..' */
637 if ((p
= strrchr(realdir
, '/')) != NULL
) {
642 strlcpy(realdir
, "..", sizeof(realdir
));
644 if (stat(realdir
, &dir
) == 0)
650 if (pdir
->st_uid
== geteuid())
651 pdirwok
= ((pdir
->st_mode
& S_IWUSR
) != 0);
652 else if (matchgroup(pdir
->st_gid
))
653 pdirwok
= ((pdir
->st_mode
& S_IWGRP
) != 0);
655 pdirwok
= ((pdir
->st_mode
& S_IWOTH
) != 0);
658 /* 'a': can APPE to file */
659 if (wok
&& CURCLASS_FLAGS_ISSET(upload
) && S_ISREG(fe
->stat
->st_mode
))
662 /* 'c': can create or append to files in directory */
663 if (wok
&& CURCLASS_FLAGS_ISSET(modify
) && S_ISDIR(fe
->stat
->st_mode
))
666 /* 'd': can delete file or directory */
667 if (pdirwok
&& CURCLASS_FLAGS_ISSET(modify
)) {
671 if (S_ISDIR(fe
->stat
->st_mode
)) {
675 if ((dirp
= opendir(fe
->display
)) == NULL
)
678 while ((dp
= readdir(dirp
)) != NULL
) {
679 if (ISDOTDIR(dp
->d_name
) ||
680 ISDOTDOTDIR(dp
->d_name
))
692 /* 'e': can enter directory */
693 if (xok
&& S_ISDIR(fe
->stat
->st_mode
))
696 /* 'f': can rename file or directory */
697 if (pdirwok
&& CURCLASS_FLAGS_ISSET(modify
))
700 /* 'l': can list directory */
701 if (rok
&& xok
&& S_ISDIR(fe
->stat
->st_mode
))
704 /* 'm': can create directory */
705 if (wok
&& CURCLASS_FLAGS_ISSET(modify
) && S_ISDIR(fe
->stat
->st_mode
))
708 /* 'p': can remove files in directory */
709 if (wok
&& CURCLASS_FLAGS_ISSET(modify
) && S_ISDIR(fe
->stat
->st_mode
))
712 /* 'r': can RETR file */
713 if (rok
&& S_ISREG(fe
->stat
->st_mode
))
716 /* 'w': can STOR file */
717 if (wok
&& CURCLASS_FLAGS_ISSET(upload
) && S_ISREG(fe
->stat
->st_mode
))
724 fact_size(const char *fact
, FILE *fd
, factelem
*fe
)
727 if (S_ISREG(fe
->stat
->st_mode
))
728 cprintf(fd
, "%s=" LLF
";", fact
, (LLT
)fe
->stat
->st_size
);
732 fact_type(const char *fact
, FILE *fd
, factelem
*fe
)
735 cprintf(fd
, "%s=", fact
);
736 switch (fe
->stat
->st_mode
& S_IFMT
) {
738 if (fe
->flags
& FE_MLSD
) {
739 if ((fe
->flags
& FE_ISCURDIR
) || ISDOTDIR(fe
->display
))
741 else if (ISDOTDOTDIR(fe
->display
))
753 cprintf(fd
, "OS.unix=fifo");
755 case S_IFLNK
: /* XXX: probably a NO-OP with stat() */
756 cprintf(fd
, "OS.unix=slink");
759 cprintf(fd
, "OS.unix=socket");
763 cprintf(fd
, "OS.unix=%s-" ULLF
"/" ULLF
,
764 S_ISBLK(fe
->stat
->st_mode
) ? "blk" : "chr",
765 (ULLT
)major(fe
->stat
->st_rdev
),
766 (ULLT
)minor(fe
->stat
->st_rdev
));
769 cprintf(fd
, "OS.unix=UNKNOWN(0%o)", fe
->stat
->st_mode
& S_IFMT
);
776 fact_unique(const char *fact
, FILE *fd
, factelem
*fe
)
778 char obuf
[(sizeof(dev_t
) + sizeof(ino_t
) + 2) * 4 / 3 + 2];
779 char tbuf
[sizeof(dev_t
) + sizeof(ino_t
)];
782 (char *)&(fe
->stat
->st_dev
), sizeof(dev_t
));
783 memcpy(tbuf
+ sizeof(dev_t
),
784 (char *)&(fe
->stat
->st_ino
), sizeof(ino_t
));
785 base64_encode(tbuf
, sizeof(dev_t
) + sizeof(ino_t
), obuf
, 1);
786 cprintf(fd
, "%s=%s;", fact
, obuf
);
790 matchgroup(gid_t gid
)
794 for (i
= 0; i
< gidcount
; i
++)
795 if (gid
== gidlist
[i
])
801 mlsname(FILE *fp
, factelem
*fe
)
803 char realfile
[MAXPATHLEN
];
807 for (i
= 0; i
< FACTTABSIZE
; i
++) {
808 if (facttab
[i
].enabled
)
809 (facttab
[i
].display
)(facttab
[i
].name
, fp
, fe
);
811 if ((fe
->flags
& FE_MLSD
) &&
812 !(fe
->flags
& FE_ISCURDIR
) && !ISDOTDIR(fe
->display
)) {
813 /* if MLSD and not "." entry, display as-is */
816 /* if MLST, or MLSD and "." entry, realpath(3) it */
817 if (realpath(fe
->display
, realfile
) != NULL
)
820 cprintf(fp
, " %s\r\n", userf
? realfile
: fe
->display
);
824 replydirname(const char *name
, const char *message
)
827 char npath
[MAXPATHLEN
* 2];
830 ep
= &npath
[sizeof(npath
) - 1];
844 reply(257, "\"%s\" %s", npath
, message
);
848 discover_path(last_path
, new_path
)
850 const char *new_path
;
852 char tp
[MAXPATHLEN
+ 1] = "";
853 char tq
[MAXPATHLEN
+ 1] = "";
858 struct stat st1
, st2
;
860 if (new_path
[0] != '/') {
861 (void)strlcpy(tp
, last_path
, MAXPATHLEN
);
862 (void)strlcat(tp
, "/", MAXPATHLEN
);
864 (void)strlcat(tp
, new_path
, MAXPATHLEN
);
865 (void)strlcat(tp
, "/", MAXPATHLEN
);
868 * resolve symlinks. A symlink may introduce another symlink, so we
869 * loop trying to resolve symlinks until we don't find any of them.
872 /* Collapse any // into / */
873 while ((cp
= strstr(tp
, "//")) != NULL
)
874 (void)memmove(cp
, cp
+ 1, strlen(cp
) - 1 + 1);
876 /* Collapse any /./ into / */
877 while ((cp
= strstr(tp
, "/./")) != NULL
)
878 (void)memmove(cp
, cp
+ 2, strlen(cp
) - 2 + 1);
883 while ((cp
= strstr(++cp
, "/")) != NULL
) {
884 sz1
= (unsigned long)cp
- (unsigned long)tp
;
885 if (sz1
> MAXPATHLEN
)
888 sz2
= readlink(tp
, tq
, MAXPATHLEN
);
891 /* If this is not a symlink, move to next / */
896 * We found a symlink, so we will have to
897 * do one more pass to check there is no
898 * more symlink in the path
903 * Null terminate the string and remove trailing /
907 if (tq
[sz2
- 1] == '/')
911 * Is this an absolute link or a relative link?
915 if (strlen(cp
) + sz2
> MAXPATHLEN
)
917 memmove(tp
+ sz2
, cp
, strlen(cp
) + 1);
921 for (cq
= cp
- 1; *cq
!= '/'; cq
--);
923 ((unsigned long)cq
- (unsigned long)cp
)
924 + 1 + sz2
> MAXPATHLEN
)
926 (void)memmove(cq
+ 1 + sz2
,
928 (void)memcpy(cq
+ 1, tq
, sz2
);
932 * start over, looking for new symlinks
936 } while (nomorelink
== 0);
938 /* Collapse any /foo/../ into /foo/ */
939 while ((cp
= strstr(tp
, "/../")) != NULL
) {
940 /* ^/../foo/ becomes ^/foo/ */
942 (void)memmove(cp
, cp
+ 3,
945 for (cq
= cp
- 1; *cq
!= '/'; cq
--);
946 (void)memmove(cq
, cp
+ 3,
951 /* strip strailing / */
953 tp
[strlen(tp
) - 1] = '\0';
955 /* check that the path is correct */
958 if ((st1
.st_dev
!= st2
.st_dev
) || (st1
.st_ino
!= st2
.st_ino
))
961 (void)strlcpy(last_path
, tp
, MAXPATHLEN
);
965 (void)strlcat(last_path
, "/", MAXPATHLEN
);
966 (void)strlcat(last_path
, new_path
, MAXPATHLEN
);