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 */
50 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 { "pass", passcmd, 0, },
100 { "pasv", pasvcmd, 1, },
101 { "pwd", pwdcmd, 0, },
102 { "port", portcmd, 1, },
103 { "quit", quitcmd, 0, },
104 { "rest", restartcmd, 1, },
105 { "retr", retrievecmd, 1, },
106 { "rmd", delcmd, 1, },
107 { "rnfr", rnfrcmd, 1, },
108 { "rnto", rntocmd, 1, },
109 { "site", sitecmd, 1, },
110 { "size", sizecmd, 1, },
111 { "stor", storecmd, 1, },
112 { "stou", storeucmd, 1, },
113 { "stru", structcmd, 1, },
114 { "syst", systemcmd, 0, },
115 { "type", typecmd, 0, },
116 { "user", usercmd, 0, },
120 #define NONENS "/lib/namespace.ftp" /* default ns for none */
122 char user[Maxpath]; /* logged in user */
123 char curdir[Maxpath]; /* current directory path */
126 int type; /* transmission type */
127 int mode; /* transmission mode */
128 int structure; /* file structure */
129 char data[64]; /* data address */
130 int pid; /* transfer process */
131 int encryption; /* encryption state */
132 int isnone, anon_ok, anon_only, anon_everybody;
133 char cputype[Maxpath]; /* the environment variable of the same name */
134 char bindir[Maxpath]; /* bin directory for this architecture */
135 char mailaddr[Maxpath];
136 char *namespace = NONENS;
139 int createperm = 0660;
141 vlong offset; /* from restart command */
145 typedef struct Passive Passive;
152 uchar ipaddr[IPaddrlen];
158 logit(char *fmt, ...)
164 rerrstr(errstr, sizeof errstr);
166 vseprint(buf, buf+sizeof(buf), fmt, arg);
168 syslog(0, FTPLOG, "%s.%s %s", nci->rsys, nci->rserv, buf);
169 werrstr(errstr, sizeof errstr);
175 syslog(0, "ftp", "usage: %s [-aAde] [-n nsfile]", argv0);
176 fprint(2, "usage: %s [-aAde] [-n nsfile]\n", argv0);
181 * read commands from the control stream and dispatch
184 main(int argc, char **argv)
194 case 'a': /* anonymous OK */
209 namespace = EARGF(usage());
215 /* open log file before doing a newns */
216 syslog(0, FTPLOG, nil);
218 /* find out who is calling */
220 nci = getnetconninfo(nil, 0);
222 nci = getnetconninfo(argv[argc-1], 0);
224 sysfatal("ftpd needs a network address");
226 strcpy(mailaddr, "?");
229 /* figure out which binaries to bind in later (only for none) */
230 arg = getenv("cputype");
232 strecpy(cputype, cputype+sizeof cputype, arg);
234 strcpy(cputype, "mips");
235 /* shurely /%s/bin */
236 snprint(bindir, sizeof(bindir), "/bin/%s/bin", cputype);
238 Binit(&in, 0, OREAD);
239 reply("220 Plan 9 FTP server ready");
241 while(cmd = Brdline(&in, '\n')){
245 * strip out trailing cr's & lf and delimit with null
251 while(i > 0 && cmd[i-1] == '\r')
255 * hack for GatorFTP+, look for a 0x10 used as a delimiter
257 p = strchr(cmd, 0x10);
262 * get rid of telnet control sequences (we don't need them)
264 while(*cmd && (uchar)*cmd == Iac){
271 * parse the message (command arg)
273 arg = strchr(cmd, ' ');
281 * ignore blank commands
287 * lookup the command and do it
289 for(p = cmd; *p; p++)
291 for(t = cmdtab; t->name; t++)
292 if(strcmp(cmd, t->name) == 0){
293 if(t->needlogin && !loggedin)
295 else if((*t->f)(arg) < 0)
299 if(t->f != restartcmd){
301 * the file offset is set to zero following
302 * all commands except the restart command
308 * the OOB bytes preceding an abort from UCB machines
309 * comes out as something unrecognizable instead of
310 * IAC's. Certainly a Plan 9 bug but I can't find it.
311 * This is a major hack to avoid the problem. -- presotto
314 if(i > 4 && strcmp(cmd+i-4, "abor") == 0){
317 logit("%s (%s) command not implemented", cmd, arg?arg:"");
318 reply("502 %s command not implemented", cmd);
324 postnote(PNPROC, pid, "kill");
331 reply(char *fmt, ...)
337 s = vseprint(buf, buf+sizeof(buf)-3, fmt, arg);
345 write(1, buf, s - buf);
352 return reply("530 Sod off, service requires login");
356 * run a command in a separate process
359 asproc(void (*f)(char*, int), char *arg, int arg2)
364 /* wait for previous command to finish */
367 if(i == pid || i < 0)
372 switch(pid = rfork(RFFDG|RFPROC|RFNOTEG)){
374 return reply("450 Out of processes: %r");
385 * run a command to filter a tail
388 transfer(char *cmd, char *a1, char *a2, char *a3, int image)
390 int n, dfd, fd, bytes, eofs, pid;
395 reply("150 Opening data connection for %s (%s)", cmd, data);
398 return reply("425 Error opening data connection: %r");
401 return reply("520 Internal Error: %r");
404 switch(pid = rfork(RFFDG|RFPROC|RFNAMEG)){
406 return reply("450 Out of processes: %r");
408 logit("running %s %s %s %s pid %d",
409 cmd, a1?a1:"", a2?a2:"" , a3?a3:"",getpid());
415 fd = open("#s/boot", ORDWR);
417 || bind("#/", "/", MAFTER) < 0
418 || amount(fd, "/bin", MREPL, "") < 0
419 || bind("#c", "/dev", MAFTER) < 0
420 || bind(bindir, "/bin", MREPL) < 0)
421 exits("building name space");
424 execl(cmd, cmd, a1, a2, a3, nil);
429 while((n = read(pfd[1], buf, sizeof buf)) >= 0){
449 n = crlfwrite(dfd, p, n);
451 n = write(dfd, p, n);
453 postnote(PNPROC, pid, "kill");
464 /* wait for this command to finish */
467 if(w == nil || w->pid == pid)
471 if(w != nil && w->msg != nil && w->msg[0] != 0){
474 logit("%s %s %s %s failed %s", cmd, a1?a1:"", a2?a2:"" , a3?a3:"", w->msg);
477 reply("226 Transfer complete");
489 reply("510 Plan 9 FTP daemon still alive");
497 loginuser(char *user, char *nsfile, int gotoslash)
499 logit("login %s %s %s %s", user, mailaddr, nci->rsys, nsfile);
500 if(nsfile != nil && newns(user, nsfile) < 0){
501 logit("namespace file %s does not exist", nsfile);
502 return reply("530 Not logged in: login out of service");
504 getwd(curdir, sizeof(curdir));
509 putenv("service", "ftp");
512 reply("230- If you have problems, send mail to 'postmaster'.");
513 return reply("230 Logged in");
522 sleep(pause); /* deter guessers */
523 if (pause < (1UL << 20))
530 * get a user id, reply with a challenge. The users 'anonymous'
531 * and 'ftp' are equivalent to 'none'. The user 'none' requires
539 logit("user %s %s", name, nci->rsys);
541 return reply("530 Already logged in as %s", user);
542 if(name == 0 || *name == 0)
543 return reply("530 user command needs user name");
549 strncpy(user, name, sizeof(user));
552 user[sizeof(user)-1] = 0;
553 if(strcmp(user, "anonymous") == 0 || strcmp(user, "ftp") == 0)
554 strcpy(user, "none");
555 else if(anon_everybody)
558 if(strcmp(user, "Administrator") == 0 || strcmp(user, "admin") == 0)
559 return reply("530 go away, script kiddie");
560 else if(strcmp(user, "*none") == 0){
562 return reply("530 Not logged in: anonymous disallowed");
563 return loginuser("none", namespace, 1);
565 else if(strcmp(user, "none") == 0){
567 return reply("530 Not logged in: anonymous disallowed");
568 return reply("331 Send email address as password");
571 return reply("530 Not logged in: anonymous access only");
573 isnoworld = noworld(name);
575 return reply("331 OK");
577 /* consult the auth server */
580 if((ch = auth_challenge("proto=p9cr role=server user=%q", user)) == nil)
581 return reply("421 %r");
582 return reply("331 encrypt challenge, %s, as a password", ch->chal);
586 * get a password, set up user if it works.
589 passcmd(char *response)
597 if(strcmp(user, "none") == 0 || strcmp(user, "*none") == 0){
598 /* for none, accept anything as a password */
600 strncpy(mailaddr, response, sizeof(mailaddr)-1);
601 return loginuser("none", namespace, 1);
605 /* noworld gets a password in the clear */
606 if(login(user, response, "/lib/namespace.noworld") < 0)
607 return reply("530 Not logged in");
609 /* login has already setup the namespace */
610 return loginuser(user, nil, 0);
612 /* for everyone else, do challenge response */
614 return reply("531 Send user id before encrypted challenge");
616 ch->nresp = strlen(response);
617 ai = auth_response(ch);
618 if(ai == nil || auth_chuid(ai, nil) < 0) {
620 return reply("530 Not logged in: %r");
625 /* if the user has specified a namespace for ftp, use it */
626 snprint(namefile, sizeof(namefile), "/usr/%s/lib/namespace.ftp", user);
627 strcpy(mailaddr, user);
629 if(access(namefile, 0) == 0)
630 return loginuser(user, namefile, 0);
632 return loginuser(user, "/lib/namespace", 0);
637 * print working directory
643 return reply("550 Pwd takes no argument");
644 return reply("257 \"%s\" is the current directory", curdir);
656 /* shell cd semantics */
657 if(dir == 0 || *dir == 0){
661 snprint(buf, sizeof buf, "/usr/%s", user);
664 if(accessok(rp) == 0)
670 return reply("550 Permission denied");
673 return reply("550 Cwd failed: %r");
675 return reply("250 directory changed to %s", curdir);
694 postnote(PNPROC, pid, "kill");
706 return reply("501 Type command needs arguments");
724 return reply("501 Unimplemented type %s", x);
727 return reply("200 Type %s", type==Tascii ? "Ascii" : "Image");
734 return reply("501 Mode command needs arguments");
736 switch(tolower(*arg)){
741 return reply("501 Unimplemented mode %c", *arg);
745 return reply("200 Stream mode");
752 return reply("501 Struct command needs arguments");
754 switch(tolower(*arg)){
759 return reply("501 Unimplemented structure %c", *arg);
762 return reply("200 File structure");
772 return reply("501 Port command needs arguments");
773 n = getfields(arg, field, 7, 0, ", ");
775 return reply("501 Incorrect port specification");
776 snprint(data, sizeof data, "tcp!%.3s.%.3s.%.3s.%.3s!%d", field[0], field[1], field[2],
777 field[3], atoi(field[4])*256 + atoi(field[5]));
778 return reply("200 Data port is %s", data);
788 if(bind("#/", "/", MAFTER) < 0){
789 logit("can't bind #/ to /: %r");
790 return reply("500 can't bind #/ to /: %r");
793 if(bind(nci->spec, "/net", MBEFORE) < 0){
794 logit("can't bind %s to /net: %r", nci->spec);
795 rv = reply("500 can't bind %s to /net: %r", nci->spec);
826 p->afd = announce("tcp!*!0", passive.adir);
829 return reply("500 No free ports");
831 nnci = getnetconninfo(p->adir, -1);
834 /* parse the local address */
836 logit("local sys is %s", nci->lsys);
837 parseip(p->ipaddr, nci->lsys);
838 if(ipcmp(p->ipaddr, v4prefix) == 0 || ipcmp(p->ipaddr, IPnoaddr) == 0)
839 parseip(p->ipaddr, nci->lsys);
840 p->port = atoi(nnci->lserv);
842 freenetconninfo(nnci);
845 return reply("227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)",
846 p->ipaddr[IPv4off+0], p->ipaddr[IPv4off+1], p->ipaddr[IPv4off+2], p->ipaddr[IPv4off+3],
847 p->port>>8, p->port&0xff);
854 int Cflag, rflag, tflag, Rflag;
864 strcpy(asc, "----------");
872 for(p = asc+1; p < asc + 10; p += 3, m<<=3){
883 listfile(Biobufhdr *b, char *name, int lflag, char *dname)
898 if(strncmp(x, "/incoming/", sizeof("/incoming/")-1) != 0)
904 strcpy(ts, ctime(d->mtime));
907 if(now - d->mtime > 6*30*24*60*60)
908 memmove(ts+11, ts+23, 5);
910 /* Unix style long listing */
917 Bprint(b, "%s %3d %-8s %-8s %7lld %s ",
918 mode2asc(d->mode), links,
919 d->uid, d->gid, d->length, ts+4);
921 if(Cflag && maxnamelen < 40){
923 pad = ((col+maxnamelen)/(maxnamelen+1))*(maxnamelen+1);
924 if(pad+maxnamelen+1 < 60){
925 Bprint(b, "%*s", pad-col+n, name);
929 Bprint(b, "\r\n%s", name);
935 Bprint(b, "%s/", dname);
936 Bprint(b, "%s\r\n", name);
941 dircomp(void *va, void *vb)
950 rv = b->mtime - a->mtime;
952 rv = strcmp(a->name, b->name);
953 return (rflag?-1:1)*rv;
956 listdir(char *name, Biobufhdr *b, int lflag, int *printname, Globlist *gl)
965 fd = open(name, OREAD);
967 Bprint(b, "can't read %s: %r\r\n", name);
973 Bprint(b, "\r\n%s:\r\n", name);
977 n = dirreadall(fd, &p);
980 for(i = 0; i < n; i++){
981 l = strlen(p[i].name);
987 /* Unix style total line */
990 for(i = 0; i < n; i++){
991 if(p[i].qid.type & QTDIR)
994 total += p[i].length;
996 Bprint(b, "total %ulld\r\n", total/512);
999 qsort(p, n, sizeof(Dir), dircomp);
1000 for(i = 0; i < n; i++){
1001 if(Rflag && (p[i].qid.type & QTDIR)){
1003 globadd(gl, name, p[i].name);
1005 listfile(b, p[i].name, lflag, dname);
1010 list(char *arg, int lflag)
1027 logit("ls %s (. = %s)", arg, curdir);
1029 /* process arguments, understand /bin/ls -l option */
1031 argv[0] = "/bin/ls";
1032 argc = getfields(arg, argv+1, Narg-2, 1, " \t") + 1;
1061 reply("425 Error opening data connection:%r");
1064 reply("150 Opened data connection (%s)", data);
1066 Binits(&bh, dfd, OWRITE, buf, sizeof(buf));
1073 for(i = 0; i < argc; i++){
1079 printname = gl->first != nil && gl->first->next != nil;
1083 for(g = gl->first; g; g = g->next)
1084 if(g->glob && (n = strlen(s_to_c(g->glob))) > maxnamelen)
1086 while(s = globiter(gl)){
1088 logit("glob %s", s);
1099 if(d->qid.type & QTDIR)
1100 listdir(s, &bh, lflag, &printname, gl);
1102 listfile(&bh, s, lflag, 0);
1109 Bprint(&bh, "\r\n");
1113 reply("226 Transfer complete (list %s)", arg);
1116 namelistcmd(char *arg)
1118 return asproc(list, arg, 0);
1123 return asproc(list, arg, 1);
1127 * fuse compatability
1135 fd = open("#c/user", OREAD);
1138 n = read(fd, buf, sizeof buf - 1);
1141 if(strcmp(buf, "none") == 0)
1155 nf = tokenize(arg, f, nelem(f));
1156 if(nf != 3 || cistrcmp(f[0], "chmod") != 0)
1157 return reply("501 bad site command");
1159 return reply("550 Permission denied");
1162 return reply("501 site chmod: file does not exist");
1164 d->mode |= strtoul(f[1], 0, 8) & 0777;
1165 r = dirwstat(f[2], d);
1168 return reply("550 Permission denied %r");
1169 return reply("200 very well, then");
1173 * return the size of the file
1182 return reply("501 Size command requires pathname");
1186 return reply("501 %r accessing %s", arg);
1187 rv = reply("213 %lld", d->length);
1193 * return the modify time of the file
1203 return reply("501 Mdtm command requires pathname");
1205 return reply("550 Permission denied");
1208 return reply("501 %r accessing %s", arg);
1209 t = gmtime(d->mtime);
1210 rv = reply("213 %4.4d%2.2d%2.2d%2.2d%2.2d%2.2d",
1211 t->year+1900, t->mon+1, t->mday,
1212 t->hour, t->min, t->sec);
1218 * set an offset to start reading a file from
1219 * only lasts for one command
1222 restartcmd(char *arg)
1225 return reply("501 Restart command requires offset");
1226 offset = atoll(arg);
1229 return reply("501 Bad offset");
1232 return reply("350 Restarting at %lld. Send STORE or RETRIEVE", offset);
1236 * send a file to the user
1239 crlfwrite(int fd, char *p, int n)
1244 for(np = buf, ep = p + n; p < ep; p++){
1249 if(write(fd, buf, np - buf) == np - buf)
1255 retrievedir(char *arg)
1262 reply("550 This file requires type binary/image");
1267 p = strrchr(s_to_c(file), '/');
1268 if(p != s_to_c(file)){
1270 chdir(s_to_c(file));
1276 n = transfer("/bin/tar", "c", p, 0, 1);
1278 logit("get %s failed", arg);
1280 logit("get %s OK %d", arg, n);
1284 retrieve(char *arg, int arg2)
1286 int dfd, fd, n, i, bytes;
1293 p = strchr(arg, '\r');
1295 logit("cr in file name", arg);
1299 fd = open(arg, OREAD);
1302 if(n > 4 && strcmp(arg+n-4, ".tar") == 0){
1306 if(d->qid.type & QTDIR){
1314 logit("get %s failed", arg);
1315 reply("550 Error opening %s: %r", arg);
1319 if(seek(fd, offset, 0) < 0){
1320 reply("550 %s: seek to %lld failed", arg, offset);
1326 if(d->qid.type & QTDIR){
1327 reply("550 %s: not a plain file.", arg);
1335 n = read(fd, buf, sizeof(buf));
1337 logit("get %s failed", arg, mailaddr, nci->rsys);
1338 reply("550 Error reading %s: %r", arg);
1344 for(p = buf, ep = &buf[n]; p < ep; p++)
1347 reply("550 This file requires type binary/image");
1351 reply("150 Opening data connection for %s (%s)", arg, data);
1354 reply("425 Error opening data connection:%r");
1363 i = write(dfd, buf, n);
1366 i = crlfwrite(dfd, buf, n);
1372 logit("get %s %r to data connection after %d", arg, bytes);
1373 reply("550 Error writing to data connection: %r");
1377 } while((n = read(fd, buf, sizeof(buf))) > 0);
1380 logit("get %s %r after %d", arg, bytes);
1384 reply("226 Transfer complete");
1385 logit("get %s OK %d", arg, bytes);
1388 retrievecmd(char *arg)
1391 return reply("501 Retrieve command requires an argument");
1394 return reply("550 Permission denied");
1396 return asproc(retrieve, arg, 0);
1400 * get a file from the user
1403 lfwrite(int fd, char *p, int n)
1408 for(np = buf, ep = p + n; p < ep; p++){
1412 if(write(fd, buf, np - buf) == np - buf)
1418 store(char *arg, int fd)
1423 reply("150 Opening data connection for %s (%s)", arg, data);
1426 reply("425 Error opening data connection:%r");
1431 while((n = read(dfd, buf, sizeof(buf))) > 0){
1434 i = write(fd, buf, n);
1437 i = lfwrite(fd, buf, n);
1443 reply("550 Error writing file");
1449 logit("put %s OK", arg);
1450 reply("226 Transfer complete");
1458 return reply("501 Store command requires an argument");
1461 return reply("550 Permission denied");
1462 if(isnone && strncmp(arg, "/incoming/", sizeof("/incoming/")-1))
1463 return reply("550 Permission denied");
1465 fd = open(arg, OWRITE);
1467 return reply("550 Error opening %s: %r", arg);
1468 if(seek(fd, offset, 0) == -1)
1469 return reply("550 Error seeking %s to %d: %r",
1472 fd = create(arg, OWRITE, createperm);
1474 return reply("550 Error creating %s: %r", arg);
1477 rv = asproc(store, arg, fd);
1482 appendcmd(char *arg)
1487 return reply("501 Append command requires an argument");
1489 return reply("550 Permission denied");
1492 return reply("550 Error creating %s: Permission denied", arg);
1493 fd = open(arg, OWRITE);
1495 fd = create(arg, OWRITE, createperm);
1497 return reply("550 Error creating %s: %r", arg);
1501 rv = asproc(store, arg, fd);
1506 storeucmd(char *arg)
1513 return reply("550 Permission denied");
1514 strncpy(name, "ftpXXXXXXXXXXX", sizeof name);
1516 fd = create(name, OWRITE, createperm);
1518 return reply("550 Error creating %s: %r", name);
1520 rv = asproc(store, name, fd);
1526 mkdircmd(char *name)
1531 return reply("501 Mkdir command requires an argument");
1533 return reply("550 Permission denied");
1534 name = abspath(name);
1536 return reply("550 Permission denied");
1537 fd = create(name, OREAD, DMDIR|0775);
1539 return reply("550 Can't create %s: %r", name);
1541 return reply("226 %s created", name);
1548 return reply("501 Rmdir/delete command requires an argument");
1550 return reply("550 Permission denied");
1551 name = abspath(name);
1553 return reply("550 Permission denied");
1554 if(remove(name) < 0)
1555 return reply("550 Can't remove %s: %r", name);
1557 return reply("226 %s removed", name);
1561 * kill off the last transfer (if the process still exists)
1568 logit("abort pid %d", pid);
1570 if(postnote(PNPROC, pid, "kill") == 0)
1571 reply("426 Command aborted");
1573 logit("postnote pid %d %r", pid);
1575 return reply("226 Abort processed");
1579 systemcmd(char *arg)
1582 return reply("215 UNIX Type: L8 Version: Plan 9");
1593 reply("214- the following commands are implemented:");
1596 for(i = 0; cmdtab[i].name; i++){
1598 reply("214-%s", buf);
1601 p = seprint(p, e, " %-5.5s", cmdtab[i].name);
1604 reply("214-%s", buf);
1610 * renaming a file takes two commands
1612 static String *filepath;
1618 return reply("550 Permission denied");
1620 return reply("501 Rename command requires an argument");
1621 from = abspath(from);
1623 return reply("550 Permission denied");
1625 filepath = s_copy(from);
1628 s_append(filepath, from);
1630 return reply("350 Rename %s to ...", s_to_c(filepath));
1640 return reply("550 Permission denied");
1642 return reply("501 Rename command requires an argument");
1645 return reply("550 Permission denied");
1646 if(filepath == nil || *(s_to_c(filepath)) == 0)
1647 return reply("503 Rnto must be preceeded by an rnfr");
1649 tp = strrchr(to, '/');
1650 fp = strrchr(s_to_c(filepath), '/');
1651 if((tp && fp == 0) || (fp && tp == 0)
1652 || (fp && tp && (fp-s_to_c(filepath) != tp-to || memcmp(s_to_c(filepath), to, tp-to))))
1653 return reply("550 Rename can't change directory");
1659 if(dirwstat(s_to_c(filepath), &nd) < 0)
1660 r = reply("550 Can't rename %s to %s: %r\n", s_to_c(filepath), to);
1662 r = reply("250 %s now %s", s_to_c(filepath), to);
1669 * to dial out we need the network file system in our
1683 fd = dial(data, "20", 0, 0);
1684 errstr(err, sizeof err);
1687 cfd = listen(passive.adir, ldir);
1689 errstr(err, sizeof err);
1692 fd = accept(cfd, ldir);
1693 errstr(err, sizeof err);
1697 logit("can't dial %s: %s", data, err);
1700 werrstr(err, sizeof err);
1705 postnote(int group, int pid, char *note)
1711 * Use #p because /proc may not be in the namespace.
1715 sprint(file, "#p/%d/note", pid);
1718 sprint(file, "#p/%d/notepg", pid);
1724 f = open(file, OWRITE);
1729 if(write(f, note, r) != r) {
1738 * to circumscribe the accessible files we have to eliminate ..'s
1739 * and resolve all names from the root. We also remove any /bin/rc
1740 * special characters to avoid later problems with executed commands.
1742 char *special = "`;| ";
1745 abspath(char *origpath)
1747 char *p, *sp, *path;
1748 static String *rpath;
1756 s_append(rpath, curdir);
1758 if(*origpath != '/'){
1759 s_append(rpath, curdir);
1760 s_append(rpath, "/");
1762 s_append(rpath, origpath);
1764 path = s_to_c(rpath);
1766 for(sp = special; *sp; sp++){
1767 p = strchr(path, *sp);
1772 cleanname(s_to_c(rpath));
1773 rpath->ptr = rpath->base+strlen(rpath->base);
1775 if(!accessok(s_to_c(rpath)))
1778 return s_to_c(rpath);
1781 typedef struct Path Path;
1795 Path *pathlevel[Maxlevel];
1798 unlinkpath(char *path, int level)
1805 for(l = &pathlevel[level]; *l; l = &(*l)->next){
1808 if(strcmp(s_to_c(p->path), path) == 0){
1814 if(++n >= Maxperlevel){
1818 memset(p, 0, sizeof *p);
1819 p->path = s_append(s, path);
1825 p = mallocz(sizeof *p, 1);
1826 p->path = s_copy(path);
1831 linkpath(Path *p, int level)
1833 p->next = pathlevel[level];
1834 pathlevel[level] = p;
1839 addpath(Path *p, int level, int ok)
1842 p->next = pathlevel[level];
1843 pathlevel[level] = p;
1847 _accessok(String *s, int level)
1852 static char httplogin[] = "/.httplogin";
1860 p = unlinkpath(s_to_c(s), lvl);
1866 cp = strrchr(s_to_c(s), '/');
1870 offset = cp - s_to_c(s);
1871 s_append(s, httplogin);
1872 if(access(s_to_c(s), AEXIST) == 0){
1878 * There's no way to shorten a String without
1879 * knowing the implementation.
1881 s->ptr = s->base+offset;
1883 addpath(p, lvl, _accessok(s, level-1));
1889 * check for a subdirectory containing .httplogin
1890 * at each level of the path.
1893 accessok(char *path)
1899 npath = s_copy(path);
1900 p = s_to_c(npath)+1;
1901 for(level = 1; level < Maxlevel; level++){
1908 r = _accessok(npath, level-1);