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)
613 if(strcmp(user, "none") == 0 || strcmp(user, "*none") == 0){
614 /* for none, accept anything as a password */
616 strncpy(mailaddr, response, sizeof(mailaddr)-1);
617 return loginuser("none", namespace, 1);
621 /* noworld gets a password in the clear */
622 if(login(user, response, "/lib/namespace.noworld") < 0)
623 return reply("530 Not logged in");
625 /* login has already setup the namespace */
626 return loginuser(user, nil, 0);
628 /* for everyone else, do challenge response */
630 return reply("531 Send user id before encrypted challenge");
632 ch->nresp = strlen(response);
633 ai = auth_response(ch);
634 if(ai == nil || auth_chuid(ai, nil) < 0) {
636 return reply("530 Not logged in: %r");
641 /* if the user has specified a namespace for ftp, use it */
642 snprint(namefile, sizeof(namefile), "/usr/%s/lib/namespace.ftp", user);
643 strcpy(mailaddr, user);
645 if(access(namefile, 0) == 0)
646 return loginuser(user, namefile, 0);
648 return loginuser(user, "/lib/namespace", 0);
653 * print working directory
659 return reply("550 Pwd takes no argument");
660 return reply("257 \"%s\" is the current directory", curdir);
672 /* shell cd semantics */
673 if(dir == 0 || *dir == 0){
677 snprint(buf, sizeof buf, "/usr/%s", user);
680 if(accessok(rp) == 0)
686 return reply("550 Permission denied");
689 return reply("550 Cwd failed: %r");
691 return reply("250 directory changed to %s", curdir);
710 postnote(PNPROC, pid, "kill");
722 return reply("501 Type command needs arguments");
740 return reply("501 Unimplemented type %s", x);
743 return reply("200 Type %s", type==Tascii ? "Ascii" : "Image");
750 return reply("501 Mode command needs arguments");
752 switch(tolower(*arg)){
757 return reply("501 Unimplemented mode %c", *arg);
761 return reply("200 Stream mode");
768 return reply("501 Struct command needs arguments");
770 switch(tolower(*arg)){
775 return reply("501 Unimplemented structure %c", *arg);
778 return reply("200 File structure");
788 return reply("501 Port command needs arguments");
789 n = getfields(arg, field, 7, 0, ", ");
791 return reply("501 Incorrect port specification");
792 snprint(data, sizeof data, "tcp!%.3s.%.3s.%.3s.%.3s!%d", field[0], field[1], field[2],
793 field[3], atoi(field[4])*256 + atoi(field[5]));
794 return reply("200 Data port is %s", data);
804 if(bind("#/", "/", MAFTER) < 0){
805 logit("can't bind #/ to /: %r");
806 return reply("500 can't bind #/ to /: %r");
809 if(bind(nci->spec, "/net", MBEFORE) < 0){
810 logit("can't bind %s to /net: %r", nci->spec);
811 rv = reply("500 can't bind %s to /net: %r", nci->spec);
842 p->afd = announce("tcp!*!0", passive.adir);
845 return reply("500 No free ports");
847 nnci = getnetconninfo(p->adir, -1);
850 /* parse the local address */
852 logit("local sys is %s", nci->lsys);
853 parseip(p->ipaddr, nci->lsys);
854 if(ipcmp(p->ipaddr, v4prefix) == 0 || ipcmp(p->ipaddr, IPnoaddr) == 0)
855 parseip(p->ipaddr, nci->lsys);
856 p->port = atoi(nnci->lserv);
858 freenetconninfo(nnci);
861 return reply("227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)",
862 p->ipaddr[IPv4off+0], p->ipaddr[IPv4off+1], p->ipaddr[IPv4off+2], p->ipaddr[IPv4off+3],
863 p->port>>8, p->port&0xff);
870 int Cflag, rflag, tflag, Rflag;
880 strcpy(asc, "----------");
888 for(p = asc+1; p < asc + 10; p += 3, m<<=3){
899 listfile(Biobufhdr *b, char *name, int lflag, char *dname)
914 if(strncmp(x, "/incoming/", sizeof("/incoming/")-1) != 0)
920 strcpy(ts, ctime(d->mtime));
923 if(now - d->mtime > 6*30*24*60*60)
924 memmove(ts+11, ts+23, 5);
926 /* Unix style long listing */
933 Bprint(b, "%s %3d %-8s %-8s %7lld %s ",
934 mode2asc(d->mode), links,
935 d->uid, d->gid, d->length, ts+4);
937 if(Cflag && maxnamelen < 40){
939 pad = ((col+maxnamelen)/(maxnamelen+1))*(maxnamelen+1);
940 if(pad+maxnamelen+1 < 60){
941 Bprint(b, "%*s", pad-col+n, name);
945 Bprint(b, "\r\n%s", name);
951 Bprint(b, "%s/", dname);
952 Bprint(b, "%s\r\n", name);
957 dircomp(void *va, void *vb)
966 rv = b->mtime - a->mtime;
968 rv = strcmp(a->name, b->name);
969 return (rflag?-1:1)*rv;
972 listdir(char *name, Biobufhdr *b, int lflag, int *printname, Globlist *gl)
981 fd = open(name, OREAD);
983 Bprint(b, "can't read %s: %r\r\n", name);
989 Bprint(b, "\r\n%s:\r\n", name);
993 n = dirreadall(fd, &p);
996 for(i = 0; i < n; i++){
997 l = strlen(p[i].name);
1003 /* Unix style total line */
1006 for(i = 0; i < n; i++){
1007 if(p[i].qid.type & QTDIR)
1010 total += p[i].length;
1012 Bprint(b, "total %ulld\r\n", total/512);
1015 qsort(p, n, sizeof(Dir), dircomp);
1016 for(i = 0; i < n; i++){
1017 if(Rflag && (p[i].qid.type & QTDIR)){
1019 globadd(gl, name, p[i].name);
1021 listfile(b, p[i].name, lflag, dname);
1026 list(char *arg, int lflag)
1043 logit("ls %s (. = %s)", arg, curdir);
1045 /* process arguments, understand /bin/ls -l option */
1047 argv[0] = "/bin/ls";
1048 argc = getfields(arg, argv+1, Narg-2, 1, " \t") + 1;
1077 reply("425 Error opening data connection: %r");
1080 reply("150 Opened data connection (%s)", data);
1082 Binits(&bh, dfd, OWRITE, buf, sizeof(buf));
1089 for(i = 0; i < argc; i++){
1095 printname = gl->first != nil && gl->first->next != nil;
1099 for(g = gl->first; g; g = g->next)
1100 if(g->glob && (n = strlen(s_to_c(g->glob))) > maxnamelen)
1102 while(s = globiter(gl)){
1104 logit("glob %s", s);
1115 if(d->qid.type & QTDIR)
1116 listdir(s, &bh, lflag, &printname, gl);
1118 listfile(&bh, s, lflag, 0);
1125 Bprint(&bh, "\r\n");
1129 reply("226 Transfer complete (list %s)", arg);
1132 namelistcmd(char *arg)
1134 return asproc(list, arg, 0);
1139 return asproc(list, arg, 1);
1143 * fuse compatability
1151 fd = open("#c/user", OREAD);
1154 n = read(fd, buf, sizeof buf - 1);
1157 if(strcmp(buf, "none") == 0)
1172 return reply("501 bad site command");
1173 nf = tokenize(arg, f, nelem(f));
1174 if(nf != 3 || cistrcmp(f[0], "chmod") != 0)
1175 return reply("501 bad site command");
1177 return reply("550 Permission denied");
1180 return reply("501 site chmod: file does not exist");
1182 d->mode |= strtoul(f[1], 0, 8) & 0777;
1183 r = dirwstat(f[2], d);
1186 return reply("550 Permission denied %r");
1187 return reply("200 very well, then");
1191 * return the size of the file
1200 return reply("501 Size command requires pathname");
1204 return reply("501 %r accessing %s", arg);
1205 rv = reply("213 %lld", d->length);
1211 * return the modify time of the file
1221 return reply("501 Mdtm command requires pathname");
1223 return reply("550 Permission denied");
1226 return reply("501 %r accessing %s", arg);
1227 t = gmtime(d->mtime);
1228 rv = reply("213 %4.4d%2.2d%2.2d%2.2d%2.2d%2.2d",
1229 t->year+1900, t->mon+1, t->mday,
1230 t->hour, t->min, t->sec);
1236 * set an offset to start reading a file from
1237 * only lasts for one command
1240 restartcmd(char *arg)
1243 return reply("501 Restart command requires offset");
1244 offset = atoll(arg);
1247 return reply("501 Bad offset");
1250 return reply("350 Restarting at %lld. Send STORE or RETRIEVE", offset);
1254 * send a file to the user
1257 crlfwrite(int fd, char *p, int n)
1262 for(np = buf, ep = p + n; p < ep; p++){
1267 if(write(fd, buf, np - buf) == np - buf)
1273 retrievedir(char *arg)
1280 reply("550 This file requires type binary/image");
1285 p = strrchr(s_to_c(file), '/');
1286 if(p != s_to_c(file)){
1288 chdir(s_to_c(file));
1294 n = transfer("/bin/tar", "c", p, 0, 1);
1296 logit("get %s failed", arg);
1298 logit("get %s OK %d", arg, n);
1302 retrieve(char *arg, int arg2)
1304 int dfd, fd, n, i, bytes;
1311 p = strchr(arg, '\r');
1313 logit("cr in file name", arg);
1317 fd = open(arg, OREAD);
1320 if(n > 4 && strcmp(arg+n-4, ".tar") == 0){
1324 if(d->qid.type & QTDIR){
1332 logit("get %s failed", arg);
1333 reply("550 Error opening %s: %r", arg);
1337 if(seek(fd, offset, 0) < 0){
1338 reply("550 %s: seek to %lld failed", arg, offset);
1344 if(d->qid.type & QTDIR){
1345 reply("550 %s: not a plain file.", arg);
1353 n = read(fd, buf, sizeof(buf));
1355 logit("get %s failed", arg, mailaddr, nci->rsys);
1356 reply("550 Error reading %s: %r", arg);
1362 for(p = buf, ep = &buf[n]; p < ep; p++)
1365 reply("550 This file requires type binary/image");
1369 reply("150 Opening data connection for %s (%s)", arg, data);
1372 reply("425 Error opening data connection: %r");
1381 i = write(dfd, buf, n);
1384 i = crlfwrite(dfd, buf, n);
1390 logit("get %s %r to data connection after %d", arg, bytes);
1391 reply("550 Error writing to data connection: %r");
1395 } while((n = read(fd, buf, sizeof(buf))) > 0);
1398 logit("get %s %r after %d", arg, bytes);
1402 reply("226 Transfer complete");
1403 logit("get %s OK %d", arg, bytes);
1406 retrievecmd(char *arg)
1409 return reply("501 Retrieve command requires an argument");
1412 return reply("550 Permission denied");
1414 return asproc(retrieve, arg, 0);
1418 * get a file from the user
1421 lfwrite(int fd, char *p, int n)
1426 for(np = buf, ep = p + n; p < ep; p++){
1430 if(write(fd, buf, np - buf) == np - buf)
1436 store(char *arg, int fd)
1441 reply("150 Opening data connection for %s (%s)", arg, data);
1444 reply("425 Error opening data connection: %r");
1449 while((n = read(dfd, buf, sizeof(buf))) > 0){
1452 i = write(fd, buf, n);
1455 i = lfwrite(fd, buf, n);
1461 reply("550 Error writing file");
1467 logit("put %s OK", arg);
1468 reply("226 Transfer complete");
1476 return reply("501 Store command requires an argument");
1479 return reply("550 Permission denied");
1480 if(isnone && strncmp(arg, "/incoming/", sizeof("/incoming/")-1))
1481 return reply("550 Permission denied");
1483 fd = open(arg, OWRITE);
1485 return reply("550 Error opening %s: %r", arg);
1486 if(seek(fd, offset, 0) == -1)
1487 return reply("550 Error seeking %s to %d: %r",
1490 fd = create(arg, OWRITE, createperm);
1492 return reply("550 Error creating %s: %r", arg);
1495 rv = asproc(store, arg, fd);
1500 appendcmd(char *arg)
1505 return reply("501 Append command requires an argument");
1507 return reply("550 Permission denied");
1510 return reply("550 Error creating %s: Permission denied", arg);
1511 fd = open(arg, OWRITE);
1513 fd = create(arg, OWRITE, createperm);
1515 return reply("550 Error creating %s: %r", arg);
1519 rv = asproc(store, arg, fd);
1524 storeucmd(char *arg)
1531 return reply("550 Permission denied");
1532 strncpy(name, "ftpXXXXXXXXXXX", sizeof name);
1534 fd = create(name, OWRITE, createperm);
1536 return reply("550 Error creating %s: %r", name);
1538 rv = asproc(store, name, fd);
1544 mkdircmd(char *name)
1549 return reply("501 Mkdir command requires an argument");
1551 return reply("550 Permission denied");
1552 name = abspath(name);
1554 return reply("550 Permission denied");
1555 fd = create(name, OREAD, DMDIR|0775);
1557 return reply("550 Can't create %s: %r", name);
1559 return reply("226 %s created", name);
1566 return reply("501 Rmdir/delete command requires an argument");
1568 return reply("550 Permission denied");
1569 name = abspath(name);
1571 return reply("550 Permission denied");
1572 if(remove(name) < 0)
1573 return reply("550 Can't remove %s: %r", name);
1575 return reply("226 %s removed", name);
1579 * kill off the last transfer (if the process still exists)
1586 logit("abort pid %d", pid);
1588 if(postnote(PNPROC, pid, "kill") == 0)
1589 reply("426 Command aborted");
1591 logit("postnote pid %d %r", pid);
1593 return reply("226 Abort processed");
1597 systemcmd(char *arg)
1600 return reply("215 UNIX Type: L8 Version: Plan 9");
1611 reply("214- the following commands are implemented:");
1615 for(i = 0; cmdtab[i].name; i++){
1617 reply("214-%s", buf);
1620 p = seprint(p, e, " %-5.5s", cmdtab[i].name);
1623 reply("214-%s", buf);
1629 * renaming a file takes two commands
1631 static String *filepath;
1637 return reply("550 Permission denied");
1639 return reply("501 Rename command requires an argument");
1640 from = abspath(from);
1642 return reply("550 Permission denied");
1644 filepath = s_copy(from);
1647 s_append(filepath, from);
1649 return reply("350 Rename %s to ...", s_to_c(filepath));
1659 return reply("550 Permission denied");
1661 return reply("501 Rename command requires an argument");
1664 return reply("550 Permission denied");
1665 if(filepath == nil || *(s_to_c(filepath)) == 0)
1666 return reply("503 Rnto must be preceeded by an rnfr");
1668 tp = strrchr(to, '/');
1669 fp = strrchr(s_to_c(filepath), '/');
1670 if((tp && fp == 0) || (fp && tp == 0)
1671 || (fp && tp && (fp-s_to_c(filepath) != tp-to || memcmp(s_to_c(filepath), to, tp-to))))
1672 return reply("550 Rename can't change directory");
1678 if(dirwstat(s_to_c(filepath), &nd) < 0)
1679 r = reply("550 Can't rename %s to %s: %r\n", s_to_c(filepath), to);
1681 r = reply("250 %s now %s", s_to_c(filepath), to);
1688 * to dial out we need the network file system in our
1702 fd = dial(data, "20", 0, 0);
1706 cfd = listen(passive.adir, ldir);
1709 fd = accept(cfd, ldir);
1714 errstr(err, sizeof err);
1716 logit("can't dial %s: %s", data, err);
1718 errstr(err, sizeof err);
1723 postnote(int group, int pid, char *note)
1729 * Use #p because /proc may not be in the namespace.
1733 sprint(file, "#p/%d/note", pid);
1736 sprint(file, "#p/%d/notepg", pid);
1742 f = open(file, OWRITE);
1747 if(write(f, note, r) != r) {
1756 * to circumscribe the accessible files we have to eliminate ..'s
1757 * and resolve all names from the root. We also remove any /bin/rc
1758 * special characters to avoid later problems with executed commands.
1760 char *special = "`;| ";
1763 abspath(char *origpath)
1765 char *p, *sp, *path;
1766 static String *rpath;
1774 s_append(rpath, curdir);
1776 if(*origpath != '/'){
1777 s_append(rpath, curdir);
1778 s_append(rpath, "/");
1780 s_append(rpath, origpath);
1782 path = s_to_c(rpath);
1784 for(sp = special; *sp; sp++){
1785 p = strchr(path, *sp);
1790 cleanname(s_to_c(rpath));
1791 rpath->ptr = rpath->base+strlen(rpath->base);
1793 if(!accessok(s_to_c(rpath)))
1796 return s_to_c(rpath);
1799 typedef struct Path Path;
1813 Path *pathlevel[Maxlevel];
1816 unlinkpath(char *path, int level)
1823 for(l = &pathlevel[level]; *l; l = &(*l)->next){
1826 if(strcmp(s_to_c(p->path), path) == 0){
1832 if(++n >= Maxperlevel){
1836 memset(p, 0, sizeof *p);
1837 p->path = s_append(s, path);
1843 p = mallocz(sizeof *p, 1);
1844 p->path = s_copy(path);
1849 linkpath(Path *p, int level)
1851 p->next = pathlevel[level];
1852 pathlevel[level] = p;
1857 addpath(Path *p, int level, int ok)
1860 p->next = pathlevel[level];
1861 pathlevel[level] = p;
1865 _accessok(String *s, int level)
1870 static char httplogin[] = "/.httplogin";
1878 p = unlinkpath(s_to_c(s), lvl);
1884 cp = strrchr(s_to_c(s), '/');
1888 offset = cp - s_to_c(s);
1889 s_append(s, httplogin);
1890 if(access(s_to_c(s), AEXIST) == 0){
1896 * There's no way to shorten a String without
1897 * knowing the implementation.
1899 s->ptr = s->base+offset;
1901 addpath(p, lvl, _accessok(s, level-1));
1907 * check for a subdirectory containing .httplogin
1908 * at each level of the path.
1911 accessok(char *path)
1917 npath = s_copy(path);
1918 p = s_to_c(npath)+1;
1919 for(level = 1; level < Maxlevel; level++){
1926 r = _accessok(npath, level-1);