13 /* telnet control character */
16 /* representation types */
20 /* transmission modes */
30 /* read/write buffer size */
33 /* maximum ms we'll wait for a command */
34 Maxwait= 1000*60*30, /* inactive for 30 minutes, we hang up */
49 int namelistcmd(char*);
59 int reply(char*, ...);
60 int restartcmd(char*);
61 int retrievecmd(char*);
73 int crlfwrite(int, char*, int);
77 typedef struct Cmd Cmd;
87 { "abor", abortcmd, 0, },
88 { "appe", appendcmd, 1, },
89 { "cdup", cdupcmd, 1, },
90 { "cwd", cwdcmd, 1, },
91 { "dele", delcmd, 1, },
92 { "help", helpcmd, 0, },
93 { "list", listcmd, 1, },
94 { "mdtm", mdtmcmd, 1, },
95 { "mkd", mkdircmd, 1, },
96 { "mode", modecmd, 0, },
97 { "nlst", namelistcmd, 1, },
98 { "noop", nopcmd, 0, },
99 { "opts", optscmd, 0, },
100 { "pass", passcmd, 0, },
101 { "pasv", pasvcmd, 1, },
102 { "pwd", pwdcmd, 0, },
103 { "port", portcmd, 1, },
104 { "quit", quitcmd, 0, },
105 { "rest", restartcmd, 1, },
106 { "retr", retrievecmd, 1, },
107 { "rmd", delcmd, 1, },
108 { "rnfr", rnfrcmd, 1, },
109 { "rnto", rntocmd, 1, },
110 { "site", sitecmd, 1, },
111 { "size", sizecmd, 1, },
112 { "stor", storecmd, 1, },
113 { "stou", storeucmd, 1, },
114 { "stru", structcmd, 1, },
115 { "syst", systemcmd, 0, },
116 { "type", typecmd, 0, },
117 { "user", usercmd, 0, },
121 #define NONENS "/lib/namespace.ftp" /* default ns for none */
123 char user[Maxpath]; /* logged in user */
124 char curdir[Maxpath]; /* current directory path */
127 int type; /* transmission type */
128 int mode; /* transmission mode */
129 int structure; /* file structure */
130 char data[64]; /* data address */
131 int pid; /* transfer process */
132 int encryption; /* encryption state */
133 int isnone, anon_ok, anon_only, anon_everybody;
134 char cputype[Maxpath]; /* the environment variable of the same name */
135 char bindir[Maxpath]; /* bin directory for this architecture */
136 char mailaddr[Maxpath];
137 char *namespace = NONENS;
140 int createperm = 0660;
142 vlong offset; /* from restart command */
146 typedef struct Passive Passive;
153 uchar ipaddr[IPaddrlen];
159 logit(char *fmt, ...)
165 vseprint(buf, buf+sizeof(buf), fmt, arg);
167 syslog(0, FTPLOG, "%s.%s %s", nci->rsys, nci->rserv, buf);
173 syslog(0, "ftp", "usage: %s [-aAde] [-n nsfile]", argv0);
174 fprint(2, "usage: %s [-aAde] [-n nsfile]\n", argv0);
179 * read commands from the control stream and dispatch
182 main(int argc, char **argv)
192 case 'a': /* anonymous OK */
207 namespace = EARGF(usage());
213 /* open log file before doing a newns */
214 syslog(0, FTPLOG, nil);
216 /* find out who is calling */
218 nci = getnetconninfo(nil, 0);
220 nci = getnetconninfo(argv[argc-1], 0);
222 sysfatal("ftpd needs a network address");
224 strcpy(mailaddr, "?");
227 /* figure out which binaries to bind in later (only for none) */
228 arg = getenv("cputype");
230 strecpy(cputype, cputype+sizeof cputype, arg);
232 strcpy(cputype, "mips");
233 /* shurely /%s/bin */
234 snprint(bindir, sizeof(bindir), "/bin/%s/bin", cputype);
236 Binit(&in, 0, OREAD);
237 reply("220 Plan 9 FTP server ready");
239 while(cmd = Brdline(&in, '\n')){
243 * strip out trailing cr's & lf and delimit with null
249 while(i > 0 && cmd[i-1] == '\r')
253 * hack for GatorFTP+, look for a 0x10 used as a delimiter
255 p = strchr(cmd, 0x10);
260 * get rid of telnet control sequences (we don't need them)
262 while(*cmd && (uchar)*cmd == Iac){
269 * parse the message (command arg)
271 arg = strchr(cmd, ' ');
279 * ignore blank commands
285 * lookup the command and do it
287 for(p = cmd; *p; p++)
289 for(t = cmdtab; t->name; t++)
290 if(strcmp(cmd, t->name) == 0){
291 if(t->needlogin && !loggedin)
293 else if((*t->f)(arg) < 0)
297 if(t->f != restartcmd){
299 * the file offset is set to zero following
300 * all commands except the restart command
306 * the OOB bytes preceding an abort from UCB machines
307 * comes out as something unrecognizable instead of
308 * IAC's. Certainly a Plan 9 bug but I can't find it.
309 * This is a major hack to avoid the problem. -- presotto
312 if(i > 4 && strcmp(cmd+i-4, "abor") == 0){
315 logit("%s (%s) command not implemented", cmd, arg?arg:"");
316 reply("502 %s command not implemented", cmd);
322 postnote(PNPROC, pid, "kill");
329 reply(char *fmt, ...)
335 s = vseprint(buf, buf+sizeof(buf)-3, fmt, arg);
343 write(1, buf, s - buf);
350 return reply("530 Sod off, service requires login");
354 * run a command in a separate process
357 asproc(void (*f)(char*, int), char *arg, int arg2)
362 /* wait for previous command to finish */
365 if(i == pid || i < 0)
370 switch(pid = rfork(RFFDG|RFPROC|RFNOTEG)){
372 return reply("450 Out of processes: %r");
383 * run a command to filter a tail
386 transfer(char *cmd, char *a1, char *a2, char *a3, int image)
388 int n, dfd, fd, bytes, eofs, pid;
393 reply("150 Opening data connection for %s (%s)", cmd, data);
396 return reply("425 Error opening data connection: %r");
399 return reply("520 Internal Error: %r");
402 switch(pid = rfork(RFFDG|RFPROC|RFNAMEG)){
404 return reply("450 Out of processes: %r");
406 logit("running %s %s %s %s pid %d",
407 cmd, a1?a1:"", a2?a2:"" , a3?a3:"",getpid());
413 fd = open("#s/boot", ORDWR);
415 || bind("#/", "/", MAFTER) < 0
416 || amount(fd, "/bin", MREPL, "") < 0
417 || bind("#c", "/dev", MAFTER) < 0
418 || bind(bindir, "/bin", MREPL) < 0)
419 exits("building name space");
422 execl(cmd, cmd, a1, a2, a3, nil);
427 while((n = read(pfd[1], buf, sizeof buf)) >= 0){
447 n = crlfwrite(dfd, p, n);
449 n = write(dfd, p, n);
451 postnote(PNPROC, pid, "kill");
462 /* wait for this command to finish */
465 if(w == nil || w->pid == pid)
469 if(w != nil && w->msg != nil && w->msg[0] != 0){
472 logit("%s %s %s %s failed %s", cmd, a1?a1:"", a2?a2:"" , a3?a3:"", w->msg);
475 reply("226 Transfer complete");
484 if(arg == 0 || *arg == 0){
485 reply("501 Syntax error in parameters or arguments");
488 if(p = strchr(arg, ' '))
490 if(cistrcmp(arg, "UTF-8") == 0 || cistrcmp(arg, "UTF8") == 0){
491 reply("200 Command okay");
494 reply("502 %s option not implemented", arg);
505 reply("510 Plan 9 FTP daemon still alive");
513 loginuser(char *user, char *nsfile, int gotoslash)
515 logit("login %s %s %s %s", user, mailaddr, nci->rsys, nsfile);
516 if(nsfile != nil && newns(user, nsfile) < 0){
517 logit("namespace file %s does not exist", nsfile);
518 return reply("530 Not logged in: login out of service");
520 getwd(curdir, sizeof(curdir));
525 putenv("service", "ftp");
528 reply("230- If you have problems, send mail to 'postmaster'.");
529 return reply("230 Logged in");
538 sleep(pause); /* deter guessers */
539 if (pause < (1UL << 20))
546 * get a user id, reply with a challenge. The users 'anonymous'
547 * and 'ftp' are equivalent to 'none'. The user 'none' requires
555 logit("user %s %s", name, nci->rsys);
557 return reply("530 Already logged in as %s", user);
558 if(name == 0 || *name == 0)
559 return reply("530 user command needs user name");
565 strncpy(user, name, sizeof(user));
568 user[sizeof(user)-1] = 0;
569 if(strcmp(user, "anonymous") == 0 || strcmp(user, "ftp") == 0)
570 strcpy(user, "none");
571 else if(anon_everybody)
574 if(strcmp(user, "Administrator") == 0 || strcmp(user, "admin") == 0)
575 return reply("530 go away, script kiddie");
576 else if(strcmp(user, "*none") == 0){
578 return reply("530 Not logged in: anonymous disallowed");
579 return loginuser("none", namespace, 1);
581 else if(strcmp(user, "none") == 0){
583 return reply("530 Not logged in: anonymous disallowed");
584 return reply("331 Send email address as password");
587 return reply("530 Not logged in: anonymous access only");
589 isnoworld = noworld(name);
591 return reply("331 OK");
593 /* consult the auth server */
596 if((ch = auth_challenge("proto=p9cr role=server user=%q", user)) == nil)
597 return reply("421 %r");
598 return reply("331 encrypt challenge, %s, as a password", ch->chal);
602 * get a password, set up user if it works.
605 passcmd(char *response)
614 if(strcmp(user, "none") == 0 || strcmp(user, "*none") == 0){
615 /* for none, accept anything as a password */
617 strncpy(mailaddr, response, sizeof(mailaddr)-1);
618 return loginuser("none", namespace, 1);
622 /* noworld gets a password in the clear */
623 if(login(user, response, "/lib/namespace.noworld") < 0)
624 return reply("530 Not logged in");
626 /* login has already setup the namespace */
627 return loginuser(user, nil, 0);
629 /* for everyone else, do challenge response */
631 return reply("531 Send user id before encrypted challenge");
633 ch->nresp = strlen(response);
634 ai = auth_response(ch);
635 if(ai == nil || auth_chuid(ai, nil) < 0) {
638 return reply("530 Not logged in: %r");
640 /* chown network connection */
650 /* if the user has specified a namespace for ftp, use it */
651 snprint(namefile, sizeof(namefile), "/usr/%s/lib/namespace.ftp", user);
652 strcpy(mailaddr, user);
654 if(access(namefile, 0) == 0)
655 return loginuser(user, namefile, 0);
657 return loginuser(user, "/lib/namespace", 0);
662 * print working directory
668 return reply("550 Pwd takes no argument");
669 return reply("257 \"%s\" is the current directory", curdir);
681 /* shell cd semantics */
682 if(dir == 0 || *dir == 0){
686 snprint(buf, sizeof buf, "/usr/%s", user);
689 if(accessok(rp) == 0)
695 return reply("550 Permission denied");
698 return reply("550 Cwd failed: %r");
700 return reply("250 directory changed to %s", curdir);
719 postnote(PNPROC, pid, "kill");
731 return reply("501 Type command needs arguments");
749 return reply("501 Unimplemented type %s", x);
752 return reply("200 Type %s", type==Tascii ? "Ascii" : "Image");
759 return reply("501 Mode command needs arguments");
761 switch(tolower(*arg)){
766 return reply("501 Unimplemented mode %c", *arg);
770 return reply("200 Stream mode");
777 return reply("501 Struct command needs arguments");
779 switch(tolower(*arg)){
784 return reply("501 Unimplemented structure %c", *arg);
787 return reply("200 File structure");
797 return reply("501 Port command needs arguments");
798 n = getfields(arg, field, 7, 0, ", ");
800 return reply("501 Incorrect port specification");
801 snprint(data, sizeof data, "tcp!%.3s.%.3s.%.3s.%.3s!%d", field[0], field[1], field[2],
802 field[3], atoi(field[4])*256 + atoi(field[5]));
803 return reply("200 Data port is %s", data);
813 if(bind("#/", "/", MAFTER) < 0){
814 logit("can't bind #/ to /: %r");
815 return reply("500 can't bind #/ to /: %r");
818 if(bind(nci->spec, "/net", MBEFORE) < 0){
819 logit("can't bind %s to /net: %r", nci->spec);
820 rv = reply("500 can't bind %s to /net: %r", nci->spec);
851 p->afd = announce("tcp!*!0", passive.adir);
854 return reply("500 No free ports");
856 nnci = getnetconninfo(p->adir, -1);
859 /* parse the local address */
861 logit("local sys is %s", nci->lsys);
862 parseip(p->ipaddr, nci->lsys);
863 if(ipcmp(p->ipaddr, v4prefix) == 0 || ipcmp(p->ipaddr, IPnoaddr) == 0)
864 parseip(p->ipaddr, nci->lsys);
865 p->port = atoi(nnci->lserv);
867 freenetconninfo(nnci);
870 return reply("227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)",
871 p->ipaddr[IPv4off+0], p->ipaddr[IPv4off+1], p->ipaddr[IPv4off+2], p->ipaddr[IPv4off+3],
872 p->port>>8, p->port&0xff);
879 int Cflag, rflag, tflag, Rflag;
889 strcpy(asc, "----------");
897 for(p = asc+1; p < asc + 10; p += 3, m<<=3){
908 listfile(Biobufhdr *b, char *name, int lflag, char *dname)
923 if(strncmp(x, "/incoming/", sizeof("/incoming/")-1) != 0)
929 strcpy(ts, ctime(d->mtime));
932 if(now - d->mtime > 6*30*24*60*60)
933 memmove(ts+11, ts+23, 5);
935 /* Unix style long listing */
942 Bprint(b, "%s %3d %-8s %-8s %7lld %s ",
943 mode2asc(d->mode), links,
944 d->uid, d->gid, d->length, ts+4);
946 if(Cflag && maxnamelen < 40){
948 pad = ((col+maxnamelen)/(maxnamelen+1))*(maxnamelen+1);
949 if(pad+maxnamelen+1 < 60){
950 Bprint(b, "%*s", pad-col+n, name);
954 Bprint(b, "\r\n%s", name);
960 Bprint(b, "%s/", dname);
961 Bprint(b, "%s\r\n", name);
966 dircomp(void *va, void *vb)
975 rv = b->mtime - a->mtime;
977 rv = strcmp(a->name, b->name);
978 return (rflag?-1:1)*rv;
981 listdir(char *name, Biobufhdr *b, int lflag, int *printname, Globlist *gl)
990 fd = open(name, OREAD);
992 Bprint(b, "can't read %s: %r\r\n", name);
998 Bprint(b, "\r\n%s:\r\n", name);
1002 n = dirreadall(fd, &p);
1005 for(i = 0; i < n; i++){
1006 l = strlen(p[i].name);
1012 /* Unix style total line */
1015 for(i = 0; i < n; i++){
1016 if(p[i].qid.type & QTDIR)
1019 total += p[i].length;
1021 Bprint(b, "total %ulld\r\n", total/512);
1024 qsort(p, n, sizeof(Dir), dircomp);
1025 for(i = 0; i < n; i++){
1026 if(Rflag && (p[i].qid.type & QTDIR)){
1028 globadd(gl, name, p[i].name);
1030 listfile(b, p[i].name, lflag, dname);
1035 list(char *arg, int lflag)
1052 logit("ls %s (. = %s)", arg, curdir);
1054 /* process arguments, understand /bin/ls -l option */
1056 argv[0] = "/bin/ls";
1057 argc = getfields(arg, argv+1, Narg-2, 1, " \t") + 1;
1086 reply("425 Error opening data connection: %r");
1089 reply("150 Opened data connection (%s)", data);
1091 Binits(&bh, dfd, OWRITE, buf, sizeof(buf));
1098 for(i = 0; i < argc; i++){
1104 printname = gl->first != nil && gl->first->next != nil;
1108 for(g = gl->first; g; g = g->next)
1109 if(g->glob && (n = strlen(s_to_c(g->glob))) > maxnamelen)
1111 while(s = globiter(gl)){
1113 logit("glob %s", s);
1124 if(d->qid.type & QTDIR)
1125 listdir(s, &bh, lflag, &printname, gl);
1127 listfile(&bh, s, lflag, 0);
1134 Bprint(&bh, "\r\n");
1138 reply("226 Transfer complete (list %s)", arg);
1141 namelistcmd(char *arg)
1143 return asproc(list, arg, 0);
1148 return asproc(list, arg, 1);
1152 * fuse compatability
1160 fd = open("#c/user", OREAD);
1163 n = read(fd, buf, sizeof buf - 1);
1166 if(strcmp(buf, "none") == 0)
1181 return reply("501 bad site command");
1182 nf = tokenize(arg, f, nelem(f));
1183 if(nf != 3 || cistrcmp(f[0], "chmod") != 0)
1184 return reply("501 bad site command");
1186 return reply("550 Permission denied");
1189 return reply("501 site chmod: file does not exist");
1191 d->mode |= strtoul(f[1], 0, 8) & 0777;
1192 r = dirwstat(f[2], d);
1195 return reply("550 Permission denied %r");
1196 return reply("200 very well, then");
1200 * return the size of the file
1209 return reply("501 Size command requires pathname");
1213 return reply("501 %r accessing %s", arg);
1214 rv = reply("213 %lld", d->length);
1220 * return the modify time of the file
1230 return reply("501 Mdtm command requires pathname");
1232 return reply("550 Permission denied");
1235 return reply("501 %r accessing %s", arg);
1236 t = gmtime(d->mtime);
1237 rv = reply("213 %4.4d%2.2d%2.2d%2.2d%2.2d%2.2d",
1238 t->year+1900, t->mon+1, t->mday,
1239 t->hour, t->min, t->sec);
1245 * set an offset to start reading a file from
1246 * only lasts for one command
1249 restartcmd(char *arg)
1252 return reply("501 Restart command requires offset");
1253 offset = atoll(arg);
1256 return reply("501 Bad offset");
1259 return reply("350 Restarting at %lld. Send STORE or RETRIEVE", offset);
1263 * send a file to the user
1266 crlfwrite(int fd, char *p, int n)
1271 for(np = buf, ep = p + n; p < ep; p++){
1276 if(write(fd, buf, np - buf) == np - buf)
1282 retrievedir(char *arg)
1289 reply("550 This file requires type binary/image");
1294 p = strrchr(s_to_c(file), '/');
1295 if(p != s_to_c(file)){
1297 chdir(s_to_c(file));
1303 n = transfer("/bin/tar", "c", p, 0, 1);
1305 logit("get %s failed", arg);
1307 logit("get %s OK %d", arg, n);
1311 retrieve(char *arg, int arg2)
1313 int dfd, fd, n, i, bytes;
1320 p = strchr(arg, '\r');
1322 logit("cr in file name", arg);
1326 fd = open(arg, OREAD);
1329 if(n > 4 && strcmp(arg+n-4, ".tar") == 0){
1333 if(d->qid.type & QTDIR){
1341 logit("get %s failed", arg);
1342 reply("550 Error opening %s: %r", arg);
1346 if(seek(fd, offset, 0) < 0){
1347 reply("550 %s: seek to %lld failed", arg, offset);
1353 if(d->qid.type & QTDIR){
1354 reply("550 %s: not a plain file.", arg);
1362 n = read(fd, buf, sizeof(buf));
1364 logit("get %s failed", arg, mailaddr, nci->rsys);
1365 reply("550 Error reading %s: %r", arg);
1371 for(p = buf, ep = &buf[n]; p < ep; p++)
1374 reply("550 This file requires type binary/image");
1378 reply("150 Opening data connection for %s (%s)", arg, data);
1381 reply("425 Error opening data connection: %r");
1390 i = write(dfd, buf, n);
1393 i = crlfwrite(dfd, buf, n);
1399 logit("get %s %r to data connection after %d", arg, bytes);
1400 reply("550 Error writing to data connection: %r");
1404 } while((n = read(fd, buf, sizeof(buf))) > 0);
1407 logit("get %s %r after %d", arg, bytes);
1411 reply("226 Transfer complete");
1412 logit("get %s OK %d", arg, bytes);
1415 retrievecmd(char *arg)
1418 return reply("501 Retrieve command requires an argument");
1421 return reply("550 Permission denied");
1423 return asproc(retrieve, arg, 0);
1427 * get a file from the user
1430 lfwrite(int fd, char *p, int n)
1435 for(np = buf, ep = p + n; p < ep; p++){
1439 if(write(fd, buf, np - buf) == np - buf)
1445 store(char *arg, int fd)
1450 reply("150 Opening data connection for %s (%s)", arg, data);
1453 reply("425 Error opening data connection: %r");
1458 while((n = read(dfd, buf, sizeof(buf))) > 0){
1461 i = write(fd, buf, n);
1464 i = lfwrite(fd, buf, n);
1470 reply("550 Error writing file");
1476 logit("put %s OK", arg);
1477 reply("226 Transfer complete");
1485 return reply("501 Store command requires an argument");
1488 return reply("550 Permission denied");
1489 if(isnone && strncmp(arg, "/incoming/", sizeof("/incoming/")-1))
1490 return reply("550 Permission denied");
1492 fd = open(arg, OWRITE);
1494 return reply("550 Error opening %s: %r", arg);
1495 if(seek(fd, offset, 0) == -1)
1496 return reply("550 Error seeking %s to %d: %r",
1499 fd = create(arg, OWRITE, createperm);
1501 return reply("550 Error creating %s: %r", arg);
1504 rv = asproc(store, arg, fd);
1509 appendcmd(char *arg)
1514 return reply("501 Append command requires an argument");
1516 return reply("550 Permission denied");
1519 return reply("550 Error creating %s: Permission denied", arg);
1520 fd = open(arg, OWRITE);
1522 fd = create(arg, OWRITE, createperm);
1524 return reply("550 Error creating %s: %r", arg);
1528 rv = asproc(store, arg, fd);
1533 storeucmd(char *arg)
1540 return reply("550 Permission denied");
1541 strncpy(name, "ftpXXXXXXXXXXX", sizeof name);
1543 fd = create(name, OWRITE, createperm);
1545 return reply("550 Error creating %s: %r", name);
1547 rv = asproc(store, name, fd);
1553 mkdircmd(char *name)
1558 return reply("501 Mkdir command requires an argument");
1560 return reply("550 Permission denied");
1561 name = abspath(name);
1563 return reply("550 Permission denied");
1564 fd = create(name, OREAD, DMDIR|0775);
1566 return reply("550 Can't create %s: %r", name);
1568 return reply("226 %s created", name);
1575 return reply("501 Rmdir/delete command requires an argument");
1577 return reply("550 Permission denied");
1578 name = abspath(name);
1580 return reply("550 Permission denied");
1581 if(remove(name) < 0)
1582 return reply("550 Can't remove %s: %r", name);
1584 return reply("226 %s removed", name);
1588 * kill off the last transfer (if the process still exists)
1595 logit("abort pid %d", pid);
1597 if(postnote(PNPROC, pid, "kill") == 0)
1598 reply("426 Command aborted");
1600 logit("postnote pid %d %r", pid);
1602 return reply("226 Abort processed");
1606 systemcmd(char *arg)
1609 return reply("215 UNIX Type: L8 Version: Plan 9");
1620 reply("214- the following commands are implemented:");
1624 for(i = 0; cmdtab[i].name; i++){
1626 reply("214-%s", buf);
1629 p = seprint(p, e, " %-5.5s", cmdtab[i].name);
1632 reply("214-%s", buf);
1638 * renaming a file takes two commands
1640 static String *filepath;
1646 return reply("550 Permission denied");
1648 return reply("501 Rename command requires an argument");
1649 from = abspath(from);
1651 return reply("550 Permission denied");
1653 filepath = s_copy(from);
1656 s_append(filepath, from);
1658 return reply("350 Rename %s to ...", s_to_c(filepath));
1668 return reply("550 Permission denied");
1670 return reply("501 Rename command requires an argument");
1673 return reply("550 Permission denied");
1674 if(filepath == nil || *(s_to_c(filepath)) == 0)
1675 return reply("503 Rnto must be preceeded by an rnfr");
1677 tp = strrchr(to, '/');
1678 fp = strrchr(s_to_c(filepath), '/');
1679 if((tp && fp == 0) || (fp && tp == 0)
1680 || (fp && tp && (fp-s_to_c(filepath) != tp-to || memcmp(s_to_c(filepath), to, tp-to))))
1681 return reply("550 Rename can't change directory");
1687 if(dirwstat(s_to_c(filepath), &nd) < 0)
1688 r = reply("550 Can't rename %s to %s: %r\n", s_to_c(filepath), to);
1690 r = reply("250 %s now %s", s_to_c(filepath), to);
1697 * to dial out we need the network file system in our
1711 fd = dial(data, "20", 0, 0);
1715 cfd = listen(passive.adir, ldir);
1718 fd = accept(cfd, ldir);
1723 errstr(err, sizeof err);
1725 logit("can't dial %s: %s", data, err);
1727 errstr(err, sizeof err);
1732 postnote(int group, int pid, char *note)
1738 * Use #p because /proc may not be in the namespace.
1742 sprint(file, "#p/%d/note", pid);
1745 sprint(file, "#p/%d/notepg", pid);
1751 f = open(file, OWRITE);
1756 if(write(f, note, r) != r) {
1765 * to circumscribe the accessible files we have to eliminate ..'s
1766 * and resolve all names from the root. We also remove any /bin/rc
1767 * special characters to avoid later problems with executed commands.
1769 char *special = "`;| ";
1772 abspath(char *origpath)
1774 char *p, *sp, *path;
1775 static String *rpath;
1783 s_append(rpath, curdir);
1785 if(*origpath != '/'){
1786 s_append(rpath, curdir);
1787 s_append(rpath, "/");
1789 s_append(rpath, origpath);
1791 path = s_to_c(rpath);
1793 for(sp = special; *sp; sp++){
1794 p = strchr(path, *sp);
1799 cleanname(s_to_c(rpath));
1800 rpath->ptr = rpath->base+strlen(rpath->base);
1802 if(!accessok(s_to_c(rpath)))
1805 return s_to_c(rpath);
1808 typedef struct Path Path;
1822 Path *pathlevel[Maxlevel];
1825 unlinkpath(char *path, int level)
1832 for(l = &pathlevel[level]; *l; l = &(*l)->next){
1835 if(strcmp(s_to_c(p->path), path) == 0){
1841 if(++n >= Maxperlevel){
1845 memset(p, 0, sizeof *p);
1846 p->path = s_append(s, path);
1852 p = mallocz(sizeof *p, 1);
1853 p->path = s_copy(path);
1858 linkpath(Path *p, int level)
1860 p->next = pathlevel[level];
1861 pathlevel[level] = p;
1866 addpath(Path *p, int level, int ok)
1869 p->next = pathlevel[level];
1870 pathlevel[level] = p;
1874 _accessok(String *s, int level)
1879 static char httplogin[] = "/.httplogin";
1887 p = unlinkpath(s_to_c(s), lvl);
1893 cp = strrchr(s_to_c(s), '/');
1897 offset = cp - s_to_c(s);
1898 s_append(s, httplogin);
1899 if(access(s_to_c(s), AEXIST) == 0){
1905 * There's no way to shorten a String without
1906 * knowing the implementation.
1908 s->ptr = s->base+offset;
1910 addpath(p, lvl, _accessok(s, level-1));
1916 * check for a subdirectory containing .httplogin
1917 * at each level of the path.
1920 accessok(char *path)
1926 npath = s_copy(path);
1927 p = s_to_c(npath)+1;
1928 for(level = 1; level < Maxlevel; level++){
1935 r = _accessok(npath, level-1);