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 rerrstr(errstr, sizeof errstr);
167 vseprint(buf, buf+sizeof(buf), fmt, arg);
169 syslog(0, FTPLOG, "%s.%s %s", nci->rsys, nci->rserv, buf);
170 werrstr(errstr, sizeof errstr);
176 syslog(0, "ftp", "usage: %s [-aAde] [-n nsfile]", argv0);
177 fprint(2, "usage: %s [-aAde] [-n nsfile]\n", argv0);
182 * read commands from the control stream and dispatch
185 main(int argc, char **argv)
195 case 'a': /* anonymous OK */
210 namespace = EARGF(usage());
216 /* open log file before doing a newns */
217 syslog(0, FTPLOG, nil);
219 /* find out who is calling */
221 nci = getnetconninfo(nil, 0);
223 nci = getnetconninfo(argv[argc-1], 0);
225 sysfatal("ftpd needs a network address");
227 strcpy(mailaddr, "?");
230 /* figure out which binaries to bind in later (only for none) */
231 arg = getenv("cputype");
233 strecpy(cputype, cputype+sizeof cputype, arg);
235 strcpy(cputype, "mips");
236 /* shurely /%s/bin */
237 snprint(bindir, sizeof(bindir), "/bin/%s/bin", cputype);
239 Binit(&in, 0, OREAD);
240 reply("220 Plan 9 FTP server ready");
242 while(cmd = Brdline(&in, '\n')){
246 * strip out trailing cr's & lf and delimit with null
252 while(i > 0 && cmd[i-1] == '\r')
256 * hack for GatorFTP+, look for a 0x10 used as a delimiter
258 p = strchr(cmd, 0x10);
263 * get rid of telnet control sequences (we don't need them)
265 while(*cmd && (uchar)*cmd == Iac){
272 * parse the message (command arg)
274 arg = strchr(cmd, ' ');
282 * ignore blank commands
288 * lookup the command and do it
290 for(p = cmd; *p; p++)
292 for(t = cmdtab; t->name; t++)
293 if(strcmp(cmd, t->name) == 0){
294 if(t->needlogin && !loggedin)
296 else if((*t->f)(arg) < 0)
300 if(t->f != restartcmd){
302 * the file offset is set to zero following
303 * all commands except the restart command
309 * the OOB bytes preceding an abort from UCB machines
310 * comes out as something unrecognizable instead of
311 * IAC's. Certainly a Plan 9 bug but I can't find it.
312 * This is a major hack to avoid the problem. -- presotto
315 if(i > 4 && strcmp(cmd+i-4, "abor") == 0){
318 logit("%s (%s) command not implemented", cmd, arg?arg:"");
319 reply("502 %s command not implemented", cmd);
325 postnote(PNPROC, pid, "kill");
332 reply(char *fmt, ...)
338 s = vseprint(buf, buf+sizeof(buf)-3, fmt, arg);
346 write(1, buf, s - buf);
353 return reply("530 Sod off, service requires login");
357 * run a command in a separate process
360 asproc(void (*f)(char*, int), char *arg, int arg2)
365 /* wait for previous command to finish */
368 if(i == pid || i < 0)
373 switch(pid = rfork(RFFDG|RFPROC|RFNOTEG)){
375 return reply("450 Out of processes: %r");
386 * run a command to filter a tail
389 transfer(char *cmd, char *a1, char *a2, char *a3, int image)
391 int n, dfd, fd, bytes, eofs, pid;
396 reply("150 Opening data connection for %s (%s)", cmd, data);
399 return reply("425 Error opening data connection: %r");
402 return reply("520 Internal Error: %r");
405 switch(pid = rfork(RFFDG|RFPROC|RFNAMEG)){
407 return reply("450 Out of processes: %r");
409 logit("running %s %s %s %s pid %d",
410 cmd, a1?a1:"", a2?a2:"" , a3?a3:"",getpid());
416 fd = open("#s/boot", ORDWR);
418 || bind("#/", "/", MAFTER) < 0
419 || amount(fd, "/bin", MREPL, "") < 0
420 || bind("#c", "/dev", MAFTER) < 0
421 || bind(bindir, "/bin", MREPL) < 0)
422 exits("building name space");
425 execl(cmd, cmd, a1, a2, a3, nil);
430 while((n = read(pfd[1], buf, sizeof buf)) >= 0){
450 n = crlfwrite(dfd, p, n);
452 n = write(dfd, p, n);
454 postnote(PNPROC, pid, "kill");
465 /* wait for this command to finish */
468 if(w == nil || w->pid == pid)
472 if(w != nil && w->msg != nil && w->msg[0] != 0){
475 logit("%s %s %s %s failed %s", cmd, a1?a1:"", a2?a2:"" , a3?a3:"", w->msg);
478 reply("226 Transfer complete");
487 if(arg == 0 || *arg == 0){
488 reply("501 Syntax error in parameters or arguments");
491 if(p = strchr(arg, ' '))
493 if(cistrcmp(arg, "UTF-8") == 0 || cistrcmp(arg, "UTF8") == 0){
494 reply("200 Command okay");
497 reply("502 %s option not implemented", arg);
508 reply("510 Plan 9 FTP daemon still alive");
516 loginuser(char *user, char *nsfile, int gotoslash)
518 logit("login %s %s %s %s", user, mailaddr, nci->rsys, nsfile);
519 if(nsfile != nil && newns(user, nsfile) < 0){
520 logit("namespace file %s does not exist", nsfile);
521 return reply("530 Not logged in: login out of service");
523 getwd(curdir, sizeof(curdir));
528 putenv("service", "ftp");
531 reply("230- If you have problems, send mail to 'postmaster'.");
532 return reply("230 Logged in");
541 sleep(pause); /* deter guessers */
542 if (pause < (1UL << 20))
549 * get a user id, reply with a challenge. The users 'anonymous'
550 * and 'ftp' are equivalent to 'none'. The user 'none' requires
558 logit("user %s %s", name, nci->rsys);
560 return reply("530 Already logged in as %s", user);
561 if(name == 0 || *name == 0)
562 return reply("530 user command needs user name");
568 strncpy(user, name, sizeof(user));
571 user[sizeof(user)-1] = 0;
572 if(strcmp(user, "anonymous") == 0 || strcmp(user, "ftp") == 0)
573 strcpy(user, "none");
574 else if(anon_everybody)
577 if(strcmp(user, "Administrator") == 0 || strcmp(user, "admin") == 0)
578 return reply("530 go away, script kiddie");
579 else if(strcmp(user, "*none") == 0){
581 return reply("530 Not logged in: anonymous disallowed");
582 return loginuser("none", namespace, 1);
584 else if(strcmp(user, "none") == 0){
586 return reply("530 Not logged in: anonymous disallowed");
587 return reply("331 Send email address as password");
590 return reply("530 Not logged in: anonymous access only");
592 isnoworld = noworld(name);
594 return reply("331 OK");
596 /* consult the auth server */
599 if((ch = auth_challenge("proto=p9cr role=server user=%q", user)) == nil)
600 return reply("421 %r");
601 return reply("331 encrypt challenge, %s, as a password", ch->chal);
605 * get a password, set up user if it works.
608 passcmd(char *response)
616 if(strcmp(user, "none") == 0 || strcmp(user, "*none") == 0){
617 /* for none, accept anything as a password */
619 strncpy(mailaddr, response, sizeof(mailaddr)-1);
620 return loginuser("none", namespace, 1);
624 /* noworld gets a password in the clear */
625 if(login(user, response, "/lib/namespace.noworld") < 0)
626 return reply("530 Not logged in");
628 /* login has already setup the namespace */
629 return loginuser(user, nil, 0);
631 /* for everyone else, do challenge response */
633 return reply("531 Send user id before encrypted challenge");
635 ch->nresp = strlen(response);
636 ai = auth_response(ch);
637 if(ai == nil || auth_chuid(ai, nil) < 0) {
639 return reply("530 Not logged in: %r");
644 /* if the user has specified a namespace for ftp, use it */
645 snprint(namefile, sizeof(namefile), "/usr/%s/lib/namespace.ftp", user);
646 strcpy(mailaddr, user);
648 if(access(namefile, 0) == 0)
649 return loginuser(user, namefile, 0);
651 return loginuser(user, "/lib/namespace", 0);
656 * print working directory
662 return reply("550 Pwd takes no argument");
663 return reply("257 \"%s\" is the current directory", curdir);
675 /* shell cd semantics */
676 if(dir == 0 || *dir == 0){
680 snprint(buf, sizeof buf, "/usr/%s", user);
683 if(accessok(rp) == 0)
689 return reply("550 Permission denied");
692 return reply("550 Cwd failed: %r");
694 return reply("250 directory changed to %s", curdir);
713 postnote(PNPROC, pid, "kill");
725 return reply("501 Type command needs arguments");
743 return reply("501 Unimplemented type %s", x);
746 return reply("200 Type %s", type==Tascii ? "Ascii" : "Image");
753 return reply("501 Mode command needs arguments");
755 switch(tolower(*arg)){
760 return reply("501 Unimplemented mode %c", *arg);
764 return reply("200 Stream mode");
771 return reply("501 Struct command needs arguments");
773 switch(tolower(*arg)){
778 return reply("501 Unimplemented structure %c", *arg);
781 return reply("200 File structure");
791 return reply("501 Port command needs arguments");
792 n = getfields(arg, field, 7, 0, ", ");
794 return reply("501 Incorrect port specification");
795 snprint(data, sizeof data, "tcp!%.3s.%.3s.%.3s.%.3s!%d", field[0], field[1], field[2],
796 field[3], atoi(field[4])*256 + atoi(field[5]));
797 return reply("200 Data port is %s", data);
807 if(bind("#/", "/", MAFTER) < 0){
808 logit("can't bind #/ to /: %r");
809 return reply("500 can't bind #/ to /: %r");
812 if(bind(nci->spec, "/net", MBEFORE) < 0){
813 logit("can't bind %s to /net: %r", nci->spec);
814 rv = reply("500 can't bind %s to /net: %r", nci->spec);
845 p->afd = announce("tcp!*!0", passive.adir);
848 return reply("500 No free ports");
850 nnci = getnetconninfo(p->adir, -1);
853 /* parse the local address */
855 logit("local sys is %s", nci->lsys);
856 parseip(p->ipaddr, nci->lsys);
857 if(ipcmp(p->ipaddr, v4prefix) == 0 || ipcmp(p->ipaddr, IPnoaddr) == 0)
858 parseip(p->ipaddr, nci->lsys);
859 p->port = atoi(nnci->lserv);
861 freenetconninfo(nnci);
864 return reply("227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)",
865 p->ipaddr[IPv4off+0], p->ipaddr[IPv4off+1], p->ipaddr[IPv4off+2], p->ipaddr[IPv4off+3],
866 p->port>>8, p->port&0xff);
873 int Cflag, rflag, tflag, Rflag;
883 strcpy(asc, "----------");
891 for(p = asc+1; p < asc + 10; p += 3, m<<=3){
902 listfile(Biobufhdr *b, char *name, int lflag, char *dname)
917 if(strncmp(x, "/incoming/", sizeof("/incoming/")-1) != 0)
923 strcpy(ts, ctime(d->mtime));
926 if(now - d->mtime > 6*30*24*60*60)
927 memmove(ts+11, ts+23, 5);
929 /* Unix style long listing */
936 Bprint(b, "%s %3d %-8s %-8s %7lld %s ",
937 mode2asc(d->mode), links,
938 d->uid, d->gid, d->length, ts+4);
940 if(Cflag && maxnamelen < 40){
942 pad = ((col+maxnamelen)/(maxnamelen+1))*(maxnamelen+1);
943 if(pad+maxnamelen+1 < 60){
944 Bprint(b, "%*s", pad-col+n, name);
948 Bprint(b, "\r\n%s", name);
954 Bprint(b, "%s/", dname);
955 Bprint(b, "%s\r\n", name);
960 dircomp(void *va, void *vb)
969 rv = b->mtime - a->mtime;
971 rv = strcmp(a->name, b->name);
972 return (rflag?-1:1)*rv;
975 listdir(char *name, Biobufhdr *b, int lflag, int *printname, Globlist *gl)
984 fd = open(name, OREAD);
986 Bprint(b, "can't read %s: %r\r\n", name);
992 Bprint(b, "\r\n%s:\r\n", name);
996 n = dirreadall(fd, &p);
999 for(i = 0; i < n; i++){
1000 l = strlen(p[i].name);
1006 /* Unix style total line */
1009 for(i = 0; i < n; i++){
1010 if(p[i].qid.type & QTDIR)
1013 total += p[i].length;
1015 Bprint(b, "total %ulld\r\n", total/512);
1018 qsort(p, n, sizeof(Dir), dircomp);
1019 for(i = 0; i < n; i++){
1020 if(Rflag && (p[i].qid.type & QTDIR)){
1022 globadd(gl, name, p[i].name);
1024 listfile(b, p[i].name, lflag, dname);
1029 list(char *arg, int lflag)
1046 logit("ls %s (. = %s)", arg, curdir);
1048 /* process arguments, understand /bin/ls -l option */
1050 argv[0] = "/bin/ls";
1051 argc = getfields(arg, argv+1, Narg-2, 1, " \t") + 1;
1080 reply("425 Error opening data connection: %r");
1083 reply("150 Opened data connection (%s)", data);
1085 Binits(&bh, dfd, OWRITE, buf, sizeof(buf));
1092 for(i = 0; i < argc; i++){
1098 printname = gl->first != nil && gl->first->next != nil;
1102 for(g = gl->first; g; g = g->next)
1103 if(g->glob && (n = strlen(s_to_c(g->glob))) > maxnamelen)
1105 while(s = globiter(gl)){
1107 logit("glob %s", s);
1118 if(d->qid.type & QTDIR)
1119 listdir(s, &bh, lflag, &printname, gl);
1121 listfile(&bh, s, lflag, 0);
1128 Bprint(&bh, "\r\n");
1132 reply("226 Transfer complete (list %s)", arg);
1135 namelistcmd(char *arg)
1137 return asproc(list, arg, 0);
1142 return asproc(list, arg, 1);
1146 * fuse compatability
1154 fd = open("#c/user", OREAD);
1157 n = read(fd, buf, sizeof buf - 1);
1160 if(strcmp(buf, "none") == 0)
1175 return reply("501 bad site command");
1176 nf = tokenize(arg, f, nelem(f));
1177 if(nf != 3 || cistrcmp(f[0], "chmod") != 0)
1178 return reply("501 bad site command");
1180 return reply("550 Permission denied");
1183 return reply("501 site chmod: file does not exist");
1185 d->mode |= strtoul(f[1], 0, 8) & 0777;
1186 r = dirwstat(f[2], d);
1189 return reply("550 Permission denied %r");
1190 return reply("200 very well, then");
1194 * return the size of the file
1203 return reply("501 Size command requires pathname");
1207 return reply("501 %r accessing %s", arg);
1208 rv = reply("213 %lld", d->length);
1214 * return the modify time of the file
1224 return reply("501 Mdtm command requires pathname");
1226 return reply("550 Permission denied");
1229 return reply("501 %r accessing %s", arg);
1230 t = gmtime(d->mtime);
1231 rv = reply("213 %4.4d%2.2d%2.2d%2.2d%2.2d%2.2d",
1232 t->year+1900, t->mon+1, t->mday,
1233 t->hour, t->min, t->sec);
1239 * set an offset to start reading a file from
1240 * only lasts for one command
1243 restartcmd(char *arg)
1246 return reply("501 Restart command requires offset");
1247 offset = atoll(arg);
1250 return reply("501 Bad offset");
1253 return reply("350 Restarting at %lld. Send STORE or RETRIEVE", offset);
1257 * send a file to the user
1260 crlfwrite(int fd, char *p, int n)
1265 for(np = buf, ep = p + n; p < ep; p++){
1270 if(write(fd, buf, np - buf) == np - buf)
1276 retrievedir(char *arg)
1283 reply("550 This file requires type binary/image");
1288 p = strrchr(s_to_c(file), '/');
1289 if(p != s_to_c(file)){
1291 chdir(s_to_c(file));
1297 n = transfer("/bin/tar", "c", p, 0, 1);
1299 logit("get %s failed", arg);
1301 logit("get %s OK %d", arg, n);
1305 retrieve(char *arg, int arg2)
1307 int dfd, fd, n, i, bytes;
1314 p = strchr(arg, '\r');
1316 logit("cr in file name", arg);
1320 fd = open(arg, OREAD);
1323 if(n > 4 && strcmp(arg+n-4, ".tar") == 0){
1327 if(d->qid.type & QTDIR){
1335 logit("get %s failed", arg);
1336 reply("550 Error opening %s: %r", arg);
1340 if(seek(fd, offset, 0) < 0){
1341 reply("550 %s: seek to %lld failed", arg, offset);
1347 if(d->qid.type & QTDIR){
1348 reply("550 %s: not a plain file.", arg);
1356 n = read(fd, buf, sizeof(buf));
1358 logit("get %s failed", arg, mailaddr, nci->rsys);
1359 reply("550 Error reading %s: %r", arg);
1365 for(p = buf, ep = &buf[n]; p < ep; p++)
1368 reply("550 This file requires type binary/image");
1372 reply("150 Opening data connection for %s (%s)", arg, data);
1375 reply("425 Error opening data connection: %r");
1384 i = write(dfd, buf, n);
1387 i = crlfwrite(dfd, buf, n);
1393 logit("get %s %r to data connection after %d", arg, bytes);
1394 reply("550 Error writing to data connection: %r");
1398 } while((n = read(fd, buf, sizeof(buf))) > 0);
1401 logit("get %s %r after %d", arg, bytes);
1405 reply("226 Transfer complete");
1406 logit("get %s OK %d", arg, bytes);
1409 retrievecmd(char *arg)
1412 return reply("501 Retrieve command requires an argument");
1415 return reply("550 Permission denied");
1417 return asproc(retrieve, arg, 0);
1421 * get a file from the user
1424 lfwrite(int fd, char *p, int n)
1429 for(np = buf, ep = p + n; p < ep; p++){
1433 if(write(fd, buf, np - buf) == np - buf)
1439 store(char *arg, int fd)
1444 reply("150 Opening data connection for %s (%s)", arg, data);
1447 reply("425 Error opening data connection: %r");
1452 while((n = read(dfd, buf, sizeof(buf))) > 0){
1455 i = write(fd, buf, n);
1458 i = lfwrite(fd, buf, n);
1464 reply("550 Error writing file");
1470 logit("put %s OK", arg);
1471 reply("226 Transfer complete");
1479 return reply("501 Store command requires an argument");
1482 return reply("550 Permission denied");
1483 if(isnone && strncmp(arg, "/incoming/", sizeof("/incoming/")-1))
1484 return reply("550 Permission denied");
1486 fd = open(arg, OWRITE);
1488 return reply("550 Error opening %s: %r", arg);
1489 if(seek(fd, offset, 0) == -1)
1490 return reply("550 Error seeking %s to %d: %r",
1493 fd = create(arg, OWRITE, createperm);
1495 return reply("550 Error creating %s: %r", arg);
1498 rv = asproc(store, arg, fd);
1503 appendcmd(char *arg)
1508 return reply("501 Append command requires an argument");
1510 return reply("550 Permission denied");
1513 return reply("550 Error creating %s: Permission denied", arg);
1514 fd = open(arg, OWRITE);
1516 fd = create(arg, OWRITE, createperm);
1518 return reply("550 Error creating %s: %r", arg);
1522 rv = asproc(store, arg, fd);
1527 storeucmd(char *arg)
1534 return reply("550 Permission denied");
1535 strncpy(name, "ftpXXXXXXXXXXX", sizeof name);
1537 fd = create(name, OWRITE, createperm);
1539 return reply("550 Error creating %s: %r", name);
1541 rv = asproc(store, name, fd);
1547 mkdircmd(char *name)
1552 return reply("501 Mkdir command requires an argument");
1554 return reply("550 Permission denied");
1555 name = abspath(name);
1557 return reply("550 Permission denied");
1558 fd = create(name, OREAD, DMDIR|0775);
1560 return reply("550 Can't create %s: %r", name);
1562 return reply("226 %s created", name);
1569 return reply("501 Rmdir/delete command requires an argument");
1571 return reply("550 Permission denied");
1572 name = abspath(name);
1574 return reply("550 Permission denied");
1575 if(remove(name) < 0)
1576 return reply("550 Can't remove %s: %r", name);
1578 return reply("226 %s removed", name);
1582 * kill off the last transfer (if the process still exists)
1589 logit("abort pid %d", pid);
1591 if(postnote(PNPROC, pid, "kill") == 0)
1592 reply("426 Command aborted");
1594 logit("postnote pid %d %r", pid);
1596 return reply("226 Abort processed");
1600 systemcmd(char *arg)
1603 return reply("215 UNIX Type: L8 Version: Plan 9");
1614 reply("214- the following commands are implemented:");
1618 for(i = 0; cmdtab[i].name; i++){
1620 reply("214-%s", buf);
1623 p = seprint(p, e, " %-5.5s", cmdtab[i].name);
1626 reply("214-%s", buf);
1632 * renaming a file takes two commands
1634 static String *filepath;
1640 return reply("550 Permission denied");
1642 return reply("501 Rename command requires an argument");
1643 from = abspath(from);
1645 return reply("550 Permission denied");
1647 filepath = s_copy(from);
1650 s_append(filepath, from);
1652 return reply("350 Rename %s to ...", s_to_c(filepath));
1662 return reply("550 Permission denied");
1664 return reply("501 Rename command requires an argument");
1667 return reply("550 Permission denied");
1668 if(filepath == nil || *(s_to_c(filepath)) == 0)
1669 return reply("503 Rnto must be preceeded by an rnfr");
1671 tp = strrchr(to, '/');
1672 fp = strrchr(s_to_c(filepath), '/');
1673 if((tp && fp == 0) || (fp && tp == 0)
1674 || (fp && tp && (fp-s_to_c(filepath) != tp-to || memcmp(s_to_c(filepath), to, tp-to))))
1675 return reply("550 Rename can't change directory");
1681 if(dirwstat(s_to_c(filepath), &nd) < 0)
1682 r = reply("550 Can't rename %s to %s: %r\n", s_to_c(filepath), to);
1684 r = reply("250 %s now %s", s_to_c(filepath), to);
1691 * to dial out we need the network file system in our
1705 fd = dial(data, "20", 0, 0);
1709 cfd = listen(passive.adir, ldir);
1712 fd = accept(cfd, ldir);
1717 errstr(err, sizeof err);
1719 logit("can't dial %s: %s", data, err);
1721 errstr(err, sizeof err);
1726 postnote(int group, int pid, char *note)
1732 * Use #p because /proc may not be in the namespace.
1736 sprint(file, "#p/%d/note", pid);
1739 sprint(file, "#p/%d/notepg", pid);
1745 f = open(file, OWRITE);
1750 if(write(f, note, r) != r) {
1759 * to circumscribe the accessible files we have to eliminate ..'s
1760 * and resolve all names from the root. We also remove any /bin/rc
1761 * special characters to avoid later problems with executed commands.
1763 char *special = "`;| ";
1766 abspath(char *origpath)
1768 char *p, *sp, *path;
1769 static String *rpath;
1777 s_append(rpath, curdir);
1779 if(*origpath != '/'){
1780 s_append(rpath, curdir);
1781 s_append(rpath, "/");
1783 s_append(rpath, origpath);
1785 path = s_to_c(rpath);
1787 for(sp = special; *sp; sp++){
1788 p = strchr(path, *sp);
1793 cleanname(s_to_c(rpath));
1794 rpath->ptr = rpath->base+strlen(rpath->base);
1796 if(!accessok(s_to_c(rpath)))
1799 return s_to_c(rpath);
1802 typedef struct Path Path;
1816 Path *pathlevel[Maxlevel];
1819 unlinkpath(char *path, int level)
1826 for(l = &pathlevel[level]; *l; l = &(*l)->next){
1829 if(strcmp(s_to_c(p->path), path) == 0){
1835 if(++n >= Maxperlevel){
1839 memset(p, 0, sizeof *p);
1840 p->path = s_append(s, path);
1846 p = mallocz(sizeof *p, 1);
1847 p->path = s_copy(path);
1852 linkpath(Path *p, int level)
1854 p->next = pathlevel[level];
1855 pathlevel[level] = p;
1860 addpath(Path *p, int level, int ok)
1863 p->next = pathlevel[level];
1864 pathlevel[level] = p;
1868 _accessok(String *s, int level)
1873 static char httplogin[] = "/.httplogin";
1881 p = unlinkpath(s_to_c(s), lvl);
1887 cp = strrchr(s_to_c(s), '/');
1891 offset = cp - s_to_c(s);
1892 s_append(s, httplogin);
1893 if(access(s_to_c(s), AEXIST) == 0){
1899 * There's no way to shorten a String without
1900 * knowing the implementation.
1902 s->ptr = s->base+offset;
1904 addpath(p, lvl, _accessok(s, level-1));
1910 * check for a subdirectory containing .httplogin
1911 * at each level of the path.
1914 accessok(char *path)
1920 npath = s_copy(path);
1921 p = s_to_c(npath)+1;
1922 for(level = 1; level < Maxlevel; level++){
1929 r = _accessok(npath, level-1);