]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/ftpd.c
merge
[plan9front.git] / sys / src / cmd / ip / ftpd.c
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <auth.h>
5 #include <ip.h>
6 #include <libsec.h>
7 #include <String.h>
8
9 #include "glob.h"
10
11 enum
12 {
13         /* telnet control character */
14         Iac=            255,
15
16         /* representation types */
17         Tascii=         0,
18         Timage=         1,
19
20         /* transmission modes */
21         Mstream=        0,
22         Mblock=         1,
23         Mpage=          2,
24
25         /* file structure */
26         Sfile=          0,
27         Sblock=         1,
28         Scompressed=    2,
29
30         /* read/write buffer size */
31         Nbuf=           4096,
32
33         /* maximum ms we'll wait for a command */
34         Maxwait=        1000*60*30,             /* inactive for 30 minutes, we hang up */
35
36         Maxpath=        512,
37 };
38
39 int     abortcmd(char*);
40 int     appendcmd(char*);
41 int     cdupcmd(char*);
42 int     cwdcmd(char*);
43 int     delcmd(char*);
44 int     helpcmd(char*);
45 int     listcmd(char*);
46 int     mdtmcmd(char*);
47 int     mkdircmd(char*);
48 int     modecmd(char*);
49 int     namelistcmd(char*);
50 int     nopcmd(char*);
51 int     optscmd(char*);
52 int     passcmd(char*);
53 int     pasvcmd(char*);
54 int     portcmd(char*);
55 int     pwdcmd(char*);
56 int     quitcmd(char*);
57 int     rnfrcmd(char*);
58 int     rntocmd(char*);
59 int     reply(char*, ...);
60 int     restartcmd(char*);
61 int     retrievecmd(char*);
62 int     sitecmd(char*);
63 int     sizecmd(char*);
64 int     storecmd(char*);
65 int     storeucmd(char*);
66 int     structcmd(char*);
67 int     systemcmd(char*);
68 int     typecmd(char*);
69 int     usercmd(char*);
70
71 int     dialdata(void);
72 char*   abspath(char*);
73 int     crlfwrite(int, char*, int);
74 int     sodoff(void);
75 int     accessok(char*);
76
77 typedef struct Cmd      Cmd;
78 struct Cmd
79 {
80         char    *name;
81         int     (*f)(char*);
82         int     needlogin;
83 };
84
85 Cmd cmdtab[] =
86 {
87         { "abor",       abortcmd,       0, },
88         { "allo",       nopcmd,         1, },
89         { "appe",       appendcmd,      1, },
90         { "cdup",       cdupcmd,        1, },
91         { "cwd",        cwdcmd,         1, },
92         { "dele",       delcmd,         1, },
93         { "help",       helpcmd,        0, },
94         { "list",       listcmd,        1, },
95         { "mdtm",       mdtmcmd,        1, },
96         { "mkd",        mkdircmd,       1, },
97         { "mode",       modecmd,        0, },
98         { "nlst",       namelistcmd,    1, },
99         { "noop",       nopcmd,         0, },
100         { "opts",       optscmd,        0, },
101         { "pass",       passcmd,        0, },
102         { "pasv",       pasvcmd,        1, },
103         { "pwd",        pwdcmd,         0, },
104         { "port",       portcmd,        1, },
105         { "quit",       quitcmd,        0, },
106         { "rest",       restartcmd,     1, },
107         { "retr",       retrievecmd,    1, },
108         { "rmd",        delcmd,         1, },
109         { "rnfr",       rnfrcmd,        1, },
110         { "rnto",       rntocmd,        1, },
111         { "site", sitecmd, 1, },
112         { "size",       sizecmd,        1, },
113         { "stor",       storecmd,       1, },
114         { "stou",       storeucmd,      1, },
115         { "stru",       structcmd,      1, },
116         { "syst",       systemcmd,      0, },
117         { "type",       typecmd,        0, },
118         { "user",       usercmd,        0, },
119         { 0, 0, 0 },
120 };
121
122 #define NONENS "/lib/namespace.ftp"     /* default ns for none */
123
124 char    user[Maxpath];          /* logged in user */
125 char    curdir[Maxpath];        /* current directory path */
126 Chalstate       *ch;
127 int     loggedin;
128 int     type;                   /* transmission type */
129 int     mode;                   /* transmission mode */
130 int     structure;              /* file structure */
131 char    data[64];               /* data address */
132 int     pid;                    /* transfer process */
133 int     encryption;             /* encryption state */
134 int     isnone, anon_ok, anon_only, anon_everybody;
135 char    cputype[Maxpath];       /* the environment variable of the same name */
136 char    bindir[Maxpath];        /* bin directory for this architecture */
137 char    mailaddr[Maxpath];
138 char    *namespace = NONENS;
139 int     debug;
140 NetConnInfo     *nci;
141 int     createperm = 0660;
142 int     isnoworld;
143 vlong   offset;                 /* from restart command */
144
145 ulong id;
146
147 typedef struct Passive Passive;
148 struct Passive
149 {
150         int     inuse;
151         char    adir[40];
152         int     afd;
153         int     port;
154         uchar   ipaddr[IPaddrlen];
155 } passive;
156
157 #define FTPLOG "ftp"
158
159 void
160 logit(char *fmt, ...)
161 {
162         char buf[8192];
163         va_list arg;
164
165         va_start(arg, fmt);
166         vseprint(buf, buf+sizeof(buf), fmt, arg);
167         va_end(arg);
168         syslog(0, FTPLOG, "%s.%s %s", nci->rsys, nci->rserv, buf);
169 }
170
171 static void
172 usage(void)
173 {
174         syslog(0, "ftp", "usage: %s [-aAde] [-n nsfile]", argv0);
175         fprint(2, "usage: %s [-aAde] [-n nsfile]\n", argv0);
176         exits("usage");
177 }
178
179 /*
180  *  read commands from the control stream and dispatch
181  */
182 void
183 main(int argc, char **argv)
184 {
185         char *cmd;
186         char *arg;
187         char *p;
188         Cmd *t;
189         Biobuf in;
190         int i;
191
192         ARGBEGIN{
193         case 'a':               /* anonymous OK */
194                 anon_ok = 1;
195                 break;
196         case 'A':
197                 anon_ok = 1;
198                 anon_only = 1;
199                 break;
200         case 'd':
201                 debug++;
202                 break;
203         case 'e':
204                 anon_ok = 1;
205                 anon_everybody = 1;
206                 break;
207         case 'n':
208                 namespace = EARGF(usage());
209                 break;
210         default:
211                 usage();
212         }ARGEND
213
214         /* open log file before doing a newns */
215         syslog(0, FTPLOG, nil);
216
217         /* find out who is calling */
218         if(argc < 1)
219                 nci = getnetconninfo(nil, 0);
220         else
221                 nci = getnetconninfo(argv[argc-1], 0);
222         if(nci == nil)
223                 sysfatal("ftpd needs a network address");
224
225         strcpy(mailaddr, "?");
226         id = getpid();
227
228         /* figure out which binaries to bind in later (only for none) */
229         arg = getenv("cputype");
230         if(arg)
231                 strecpy(cputype, cputype+sizeof cputype, arg);
232         else
233                 strcpy(cputype, "mips");
234         /* shurely /%s/bin */
235         snprint(bindir, sizeof(bindir), "/bin/%s/bin", cputype);
236
237         Binit(&in, 0, OREAD);
238         reply("220 Plan 9 FTP server ready");
239         alarm(Maxwait);
240         while(cmd = Brdline(&in, '\n')){
241                 alarm(0);
242
243                 /*
244                  *  strip out trailing cr's & lf and delimit with null
245                  */
246                 i = Blinelen(&in)-1;
247                 cmd[i] = 0;
248                 if(debug)
249                         logit("%s", cmd);
250                 while(i > 0 && cmd[i-1] == '\r')
251                         cmd[--i] = 0;
252
253                 /*
254                  *  hack for GatorFTP+, look for a 0x10 used as a delimiter
255                  */
256                 p = strchr(cmd, 0x10);
257                 if(p)
258                         *p = 0;
259
260                 /*
261                  *  get rid of telnet control sequences (we don't need them)
262                  */
263                 while(*cmd && (uchar)*cmd == Iac){
264                         cmd++;
265                         if(*cmd)
266                                 cmd++;
267                 }
268
269                 /*
270                  *  parse the message (command arg)
271                  */
272                 arg = strchr(cmd, ' ');
273                 if(arg){
274                         *arg++ = 0;
275                         while(*arg == ' ')
276                                 arg++;
277                 }
278
279                 /*
280                  *  ignore blank commands
281                  */
282                 if(*cmd == 0)
283                         continue;
284
285                 /*
286                  *  lookup the command and do it
287                  */
288                 for(p = cmd; *p; p++)
289                         *p = tolower(*p);
290                 for(t = cmdtab; t->name; t++)
291                         if(strcmp(cmd, t->name) == 0){
292                                 if(t->needlogin && !loggedin)
293                                         sodoff();
294                                 else if((*t->f)(arg) < 0)
295                                         exits(0);
296                                 break;
297                         }
298                 if(t->f != restartcmd){
299                         /*
300                          *  the file offset is set to zero following
301                          *  all commands except the restart command
302                          */
303                         offset = 0;
304                 }
305                 if(t->name == 0){
306                         /*
307                          *  the OOB bytes preceding an abort from UCB machines
308                          *  comes out as something unrecognizable instead of
309                          *  IAC's.  Certainly a Plan 9 bug but I can't find it.
310                          *  This is a major hack to avoid the problem. -- presotto
311                          */
312                         i = strlen(cmd);
313                         if(i > 4 && strcmp(cmd+i-4, "abor") == 0){
314                                 abortcmd(0);
315                         } else{
316                                 logit("%s (%s) command not implemented", cmd, arg?arg:"");
317                                 reply("502 %s command not implemented", cmd);
318                         }
319                 }
320                 alarm(Maxwait);
321         }
322         if(pid)
323                 postnote(PNPROC, pid, "kill");
324 }
325
326 /*
327  *  reply to a command
328  */
329 int
330 reply(char *fmt, ...)
331 {
332         va_list arg;
333         char buf[8192], *s;
334
335         va_start(arg, fmt);
336         s = vseprint(buf, buf+sizeof(buf)-3, fmt, arg);
337         va_end(arg);
338         if(debug){
339                 *s = 0;
340                 logit("%s", buf);
341         }
342         *s++ = '\r';
343         *s++ = '\n';
344         write(1, buf, s - buf);
345         return 0;
346 }
347
348 int
349 sodoff(void)
350 {
351         return reply("530 Sod off, service requires login");
352 }
353
354 /*
355  *  run a command in a separate process
356  */
357 int
358 asproc(void (*f)(char*, int), char *arg, int arg2)
359 {
360         int i;
361
362         if(pid){
363                 /* wait for previous command to finish */
364                 for(;;){
365                         i = waitpid();
366                         if(i == pid || i < 0)
367                                 break;
368                 }
369         }
370
371         switch(pid = rfork(RFFDG|RFPROC|RFNOTEG)){
372         case -1:
373                 return reply("450 Out of processes: %r");
374         case 0:
375                 (*f)(arg, arg2);
376                 exits(0);
377         default:
378                 break;
379         }
380         return 0;
381 }
382
383 /*
384  * run a command to filter a tail
385  */
386 int
387 transfer(char *cmd, char *a1, char *a2, char *a3, int image)
388 {
389         int n, dfd, fd, bytes, eofs, pid;
390         int pfd[2];
391         char buf[Nbuf], *p;
392         Waitmsg *w;
393
394         reply("150 Opening data connection for %s (%s)", cmd, data);
395         dfd = dialdata();
396         if(dfd < 0)
397                 return reply("425 Error opening data connection: %r");
398
399         if(pipe(pfd) < 0)
400                 return reply("520 Internal Error: %r");
401
402         bytes = 0;
403         switch(pid = rfork(RFFDG|RFPROC|RFNAMEG)){
404         case -1:
405                 return reply("450 Out of processes: %r");
406         case 0:
407                 logit("running %s %s %s %s pid %d",
408                         cmd, a1?a1:"", a2?a2:"" , a3?a3:"",getpid());
409                 close(pfd[1]);
410                 close(dfd);
411                 dup(pfd[0], 1);
412                 dup(pfd[0], 2);
413                 if(isnone){
414                         fd = open("#s/boot", ORDWR);
415                         if(fd < 0
416                         || bind("#/", "/", MAFTER) == -1
417                         || amount(fd, "/bin", MREPL, "") == -1
418                         || bind("#c", "/dev", MAFTER) == -1
419                         || bind(bindir, "/bin", MREPL) == -1)
420                                 exits("building name space");
421                         close(fd);
422                 }
423                 execl(cmd, cmd, a1, a2, a3, nil);
424                 exits(cmd);
425         default:
426                 close(pfd[0]);
427                 eofs = 0;
428                 while((n = read(pfd[1], buf, sizeof buf)) >= 0){
429                         if(n == 0){
430                                 if(eofs++ > 5)
431                                         break;
432                                 else
433                                         continue;
434                         }
435                         eofs = 0;
436                         p = buf;
437                         if(offset > 0){
438                                 if(n > offset){
439                                         p = buf+offset;
440                                         n -= offset;
441                                         offset = 0;
442                                 } else {
443                                         offset -= n;
444                                         continue;
445                                 }
446                         }
447                         if(!image)
448                                 n = crlfwrite(dfd, p, n);
449                         else
450                                 n = write(dfd, p, n);
451                         if(n < 0){
452                                 postnote(PNPROC, pid, "kill");
453                                 bytes = -1;
454                                 break;
455                         }
456                         bytes += n;
457                 }
458                 close(pfd[1]);
459                 close(dfd);
460                 break;
461         }
462
463         /* wait for this command to finish */
464         for(;;){
465                 w = wait();
466                 if(w == nil || w->pid == pid)
467                         break;
468                 free(w);
469         }
470         if(w != nil && w->msg != nil && w->msg[0] != 0){
471                 bytes = -1;
472                 logit("%s", w->msg);
473                 logit("%s %s %s %s failed %s", cmd, a1?a1:"", a2?a2:"" , a3?a3:"", w->msg);
474         }
475         free(w);
476         reply("226 Transfer complete");
477         return bytes;
478 }
479
480 int
481 optscmd(char *arg)
482 {
483         char *p;
484
485         if(arg == 0 || *arg == 0){
486                 reply("501 Syntax error in parameters or arguments");
487                 return 0;
488         }
489         if(p = strchr(arg, ' '))
490                 *p = 0;
491         if(cistrcmp(arg, "UTF-8") == 0 || cistrcmp(arg, "UTF8") == 0){
492                 reply("200 Command okay");
493                 return 0;
494         }
495         reply("502 %s option not implemented", arg);
496         return 0;
497 }
498
499 /*
500  *  just reply OK
501  */
502 int
503 nopcmd(char *arg)
504 {
505         USED(arg);
506         reply("510 Plan 9 FTP daemon still alive");
507         return 0;
508 }
509
510 /*
511  *  login as user
512  */
513 int
514 loginuser(char *user, char *nsfile, int gotoslash)
515 {
516         logit("login %s %s %s %s", user, mailaddr, nci->rsys, nsfile);
517         if(nsfile != nil && newns(user, nsfile) < 0){
518                 logit("namespace file %s does not exist", nsfile);
519                 return reply("530 Not logged in: login out of service");
520         }
521         getwd(curdir, sizeof(curdir));
522         if(gotoslash){
523                 chdir("/");
524                 strcpy(curdir, "/");
525         }
526         putenv("service", "ftp");
527         loggedin = 1;
528         if(debug == 0)
529                 reply("230- If you have problems, send mail to 'postmaster'.");
530         return reply("230 Logged in");
531 }
532
533 static void
534 slowdown(void)
535 {
536         static ulong pause;
537
538         if (pause) {
539                 sleep(pause);                   /* deter guessers */
540                 if (pause < (1UL << 20))
541                         pause *= 2;
542         } else
543                 pause = 1000;
544 }
545
546 /*
547  *  get a user id, reply with a challenge.  The users 'anonymous'
548  *  and 'ftp' are equivalent to 'none'.  The user 'none' requires
549  *  no challenge.
550  */
551 int
552 usercmd(char *name)
553 {
554         slowdown();
555
556         logit("user %s %s", name, nci->rsys);
557         if(loggedin)
558                 return reply("530 Already logged in as %s", user);
559         if(name == 0 || *name == 0)
560                 return reply("530 user command needs user name");
561         isnoworld = 0;
562         if(*name == ':'){
563                 debug = 1;
564                 name++;
565         }
566         strncpy(user, name, sizeof(user));
567         if(debug)
568                 logit("debugging");
569         user[sizeof(user)-1] = 0;
570         if(strcmp(user, "anonymous") == 0 || strcmp(user, "ftp") == 0)
571                 strcpy(user, "none");
572         else if(anon_everybody)
573                 strcpy(user,"none");
574
575         if(strcmp(user, "Administrator") == 0 || strcmp(user, "admin") == 0)
576                 return reply("530 go away, script kiddie");
577         else if(strcmp(user, "*none") == 0){
578                 if(!anon_ok)
579                         return reply("530 Not logged in: anonymous disallowed");
580                 return loginuser("none", namespace, 1);
581         }
582         else if(strcmp(user, "none") == 0){
583                 if(!anon_ok)
584                         return reply("530 Not logged in: anonymous disallowed");
585                 return reply("331 Send email address as password");
586         }
587         else if(anon_only)
588                 return reply("530 Not logged in: anonymous access only");
589
590         isnoworld = noworld(name);
591         if(isnoworld)
592                 return reply("331 OK");
593
594         /* consult the auth server */
595         if(ch)
596                 auth_freechal(ch);
597         if((ch = auth_challenge("proto=p9cr role=server user=%q", user)) == nil)
598                 return reply("421 %r");
599         return reply("331 encrypt challenge, %s, as a password", ch->chal);
600 }
601
602 /*
603  *  get a password, set up user if it works.
604  */
605 int
606 passcmd(char *response)
607 {
608         char namefile[128];
609         AuthInfo *ai;
610         Dir nd;
611
612         if(response == nil)
613                 response = "";
614
615         if(strcmp(user, "none") == 0 || strcmp(user, "*none") == 0){
616                 /* for none, accept anything as a password */
617                 isnone = 1;
618                 strncpy(mailaddr, response, sizeof(mailaddr)-1);
619                 return loginuser("none", namespace, 1);
620         }
621
622         if(isnoworld){
623                 /* noworld gets a password in the clear */
624                 if(login(user, response, "/lib/namespace.noworld") < 0)
625                         return reply("530 Not logged in");
626                 createperm = 0664;
627                 /* login has already setup the namespace */
628                 return loginuser(user, nil, 0);
629         } else {
630                 /* for everyone else, do challenge response */
631                 if(ch == nil)
632                         return reply("531 Send user id before encrypted challenge");
633                 ch->resp = response;
634                 ch->nresp = strlen(response);
635                 ai = auth_response(ch);
636                 if(ai == nil || auth_chuid(ai, nil) < 0) {
637                         auth_freeAI(ai);
638                         slowdown();
639                         return reply("530 Not logged in: %r");
640                 }
641                 /* chown network connection */
642                 nulldir(&nd);
643                 nd.mode = 0660;
644                 nd.uid = ai->cuid;
645                 dirfwstat(0, &nd);
646
647                 auth_freeAI(ai);
648                 auth_freechal(ch);
649                 ch = nil;
650
651                 /* if the user has specified a namespace for ftp, use it */
652                 snprint(namefile, sizeof(namefile), "/usr/%s/lib/namespace.ftp", user);
653                 strcpy(mailaddr, user);
654                 createperm = 0660;
655                 if(access(namefile, 0) == 0)
656                         return loginuser(user, namefile, 0);
657                 else
658                         return loginuser(user, "/lib/namespace", 0);
659         }
660 }
661
662 /*
663  *  print working directory
664  */
665 int
666 pwdcmd(char *arg)
667 {
668         if(arg)
669                 return reply("550 Pwd takes no argument");
670         return reply("257 \"%s\" is the current directory", curdir);
671 }
672
673 /*
674  *  chdir
675  */
676 int
677 cwdcmd(char *dir)
678 {
679         char *rp;
680         char buf[Maxpath];
681
682         /* shell cd semantics */
683         if(dir == 0 || *dir == 0){
684                 if(isnone)
685                         rp = "/";
686                 else {
687                         snprint(buf, sizeof buf, "/usr/%s", user);
688                         rp = buf;
689                 }
690                 if(accessok(rp) == 0)
691                         rp = nil;
692         } else
693                 rp = abspath(dir);
694
695         if(rp == nil)
696                 return reply("550 Permission denied");
697
698         if(chdir(rp) < 0)
699                 return reply("550 Cwd failed: %r");
700         strcpy(curdir, rp);
701         return reply("250 directory changed to %s", curdir);
702 }
703
704 /*
705  *  chdir ..
706  */
707 int
708 cdupcmd(char *dp)
709 {
710         USED(dp);
711         return cwdcmd("..");
712 }
713
714 int
715 quitcmd(char *arg)
716 {
717         USED(arg);
718         reply("200 Bye");
719         if(pid)
720                 postnote(PNPROC, pid, "kill");
721         return -1;
722 }
723
724 int
725 typecmd(char *arg)
726 {
727         int c;
728         char *x;
729
730         x = arg;
731         if(arg == 0)
732                 return reply("501 Type command needs arguments");
733
734         while(c = *arg++){
735                 switch(tolower(c)){
736                 case 'a':
737                         type = Tascii;
738                         break;
739                 case 'i':
740                 case 'l':
741                         type = Timage;
742                         break;
743                 case '8':
744                 case ' ':
745                 case 'n':
746                 case 't':
747                 case 'c':
748                         break;
749                 default:
750                         return reply("501 Unimplemented type %s", x);
751                 }
752         }
753         return reply("200 Type %s", type==Tascii ? "Ascii" : "Image");
754 }
755
756 int
757 modecmd(char *arg)
758 {
759         if(arg == 0)
760                 return reply("501 Mode command needs arguments");
761         while(*arg){
762                 switch(tolower(*arg)){
763                 case 's':
764                         mode = Mstream;
765                         break;
766                 default:
767                         return reply("501 Unimplemented mode %c", *arg);
768                 }
769                 arg++;
770         }
771         return reply("200 Stream mode");
772 }
773
774 int
775 structcmd(char *arg)
776 {
777         if(arg == 0)
778                 return reply("501 Struct command needs arguments");
779         for(; *arg; arg++){
780                 switch(tolower(*arg)){
781                 case 'f':
782                         structure = Sfile;
783                         break;
784                 default:
785                         return reply("501 Unimplemented structure %c", *arg);
786                 }
787         }
788         return reply("200 File structure");
789 }
790
791 int
792 portcmd(char *arg)
793 {
794         char *field[7];
795         int n;
796
797         if(arg == 0)
798                 return reply("501 Port command needs arguments");
799         n = getfields(arg, field, 7, 0, ", ");
800         if(n != 6)
801                 return reply("501 Incorrect port specification");
802         snprint(data, sizeof data, "tcp!%.3s.%.3s.%.3s.%.3s!%d", field[0], field[1], field[2],
803                 field[3], atoi(field[4])*256 + atoi(field[5]));
804         return reply("200 Data port is %s", data);
805 }
806
807 int
808 mountnet(void)
809 {
810         int rv;
811
812         rv = 0;
813
814         if(bind("#/", "/", MAFTER) == -1){
815                 logit("can't bind #/ to /: %r");
816                 return reply("500 can't bind #/ to /: %r");
817         }
818
819         if(bind(nci->spec, "/net", MBEFORE) == -1){
820                 logit("can't bind %s to /net: %r", nci->spec);
821                 rv = reply("500 can't bind %s to /net: %r", nci->spec);
822                 unmount("#/", "/");
823         }
824
825         return rv;
826 }
827
828 void
829 unmountnet(void)
830 {
831         unmount(0, "/net");
832         unmount("#/", "/");
833 }
834
835 int
836 pasvcmd(char *arg)
837 {
838         NetConnInfo *nnci;
839         Passive *p;
840
841         USED(arg);
842         p = &passive;
843
844         if(p->inuse){
845                 close(p->afd);
846                 p->inuse = 0;
847         }
848
849         if(mountnet() < 0)
850                 return 0;
851
852         p->afd = announce("tcp!*!0", passive.adir);
853         if(p->afd < 0){
854                 unmountnet();
855                 return reply("500 No free ports");
856         }
857         nnci = getnetconninfo(p->adir, -1);
858         unmountnet();
859
860         /* parse the local address */
861         if(debug)
862                 logit("local sys is %s", nci->lsys);
863         parseip(p->ipaddr, nci->lsys);
864         if(ipcmp(p->ipaddr, v4prefix) == 0 || ipcmp(p->ipaddr, IPnoaddr) == 0)
865                 parseip(p->ipaddr, nci->lsys);
866         p->port = atoi(nnci->lserv);
867
868         freenetconninfo(nnci);
869         p->inuse = 1;
870
871         return reply("227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)",
872                 p->ipaddr[IPv4off+0], p->ipaddr[IPv4off+1], p->ipaddr[IPv4off+2], p->ipaddr[IPv4off+3],
873                 p->port>>8, p->port&0xff);
874 }
875
876 enum
877 {
878         Narg=32,
879 };
880 int Cflag, rflag, tflag, Rflag;
881 int maxnamelen;
882 int col;
883
884 char*
885 mode2asc(int m)
886 {
887         static char asc[12];
888         char *p;
889
890         strcpy(asc, "----------");
891         if(DMDIR & m)
892                 asc[0] = 'd';
893         if(DMAPPEND & m)
894                 asc[0] = 'a';
895         else if(DMEXCL & m)
896                 asc[3] = 'l';
897
898         for(p = asc+1; p < asc + 10; p += 3, m<<=3){
899                 if(m & 0400)
900                         p[0] = 'r';
901                 if(m & 0200)
902                         p[1] = 'w';
903                 if(m & 0100)
904                         p[2] = 'x';
905         }
906         return asc;
907 }
908 void
909 listfile(Biobufhdr *b, char *name, int lflag, char *dname)
910 {
911         char ts[32];
912         int n, links, pad;
913         long now;
914         char *x;
915         Dir *d;
916
917         x = abspath(name);
918         if(x == nil)
919                 return;
920         d = dirstat(x);
921         if(d == nil)
922                 return;
923         if(isnone){
924                 if(strncmp(x, "/incoming/", sizeof("/incoming/")-1) != 0)
925                         d->mode &= ~0222;
926                 d->uid = "none";
927                 d->gid = "none";
928         }
929
930         strcpy(ts, ctime(d->mtime));
931         ts[16] = 0;
932         now = time(0);
933         if(now - d->mtime > 6*30*24*60*60)
934                 memmove(ts+11, ts+23, 5);
935         if(lflag){
936                 /* Unix style long listing */
937                 if(DMDIR&d->mode){
938                         links = 2;
939                         d->length = 512;
940                 } else
941                         links = 1;
942
943                 Bprint(b, "%s %3d %-8s %-8s %7lld %s ",
944                         mode2asc(d->mode), links,
945                         d->uid, d->gid, d->length, ts+4);
946         }
947         if(Cflag && maxnamelen < 40){
948                 n = strlen(name);
949                 pad = ((col+maxnamelen)/(maxnamelen+1))*(maxnamelen+1);
950                 if(pad+maxnamelen+1 < 60){
951                         Bprint(b, "%*s", pad-col+n, name);
952                         col = pad+n;
953                 }
954                 else{
955                         Bprint(b, "\r\n%s", name);
956                         col = n;
957                 }
958         }
959         else{
960                 if(dname)
961                         Bprint(b, "%s/", dname);
962                 Bprint(b, "%s\r\n", name);
963         }
964         free(d);
965 }
966 int
967 dircomp(void *va, void *vb)
968 {
969         int rv;
970         Dir *a, *b;
971
972         a = va;
973         b = vb;
974
975         if(tflag)
976                 rv = b->mtime - a->mtime;
977         else
978                 rv = strcmp(a->name, b->name);
979         return (rflag?-1:1)*rv;
980 }
981 void
982 listdir(char *name, Biobufhdr *b, int lflag, int *printname, Globlist *gl)
983 {
984         Dir *p;
985         int fd, n, i, l;
986         char *dname;
987         uvlong total;
988
989         col = 0;
990
991         fd = open(name, OREAD);
992         if(fd < 0){
993                 Bprint(b, "can't read %s: %r\r\n", name);
994                 return;
995         }
996         dname = 0;
997         if(*printname){
998                 if(Rflag || lflag)
999                         Bprint(b, "\r\n%s:\r\n", name);
1000                 else
1001                         dname = name;
1002         }
1003         n = dirreadall(fd, &p);
1004         close(fd);
1005         if(Cflag){
1006                 for(i = 0; i < n; i++){
1007                         l = strlen(p[i].name);
1008                         if(l > maxnamelen)
1009                                 maxnamelen = l;
1010                 }
1011         }
1012
1013         /* Unix style total line */
1014         if(lflag){
1015                 total = 0;
1016                 for(i = 0; i < n; i++){
1017                         if(p[i].qid.type & QTDIR)
1018                                 total += 512;
1019                         else
1020                                 total += p[i].length;
1021                 }
1022                 Bprint(b, "total %ulld\r\n", total/512);
1023         }
1024
1025         qsort(p, n, sizeof(Dir), dircomp);
1026         for(i = 0; i < n; i++){
1027                 if(Rflag && (p[i].qid.type & QTDIR)){
1028                         *printname = 1;
1029                         globadd(gl, name, p[i].name);
1030                 }
1031                 listfile(b, p[i].name, lflag, dname);
1032         }
1033         free(p);
1034 }
1035 void
1036 list(char *arg, int lflag)
1037 {
1038         Dir *d;
1039         Globlist *gl;
1040         Glob *g;
1041         int dfd, printname;
1042         int i, n, argc;
1043         char *alist[Narg];
1044         char **argv;
1045         Biobufhdr bh;
1046         uchar buf[512];
1047         char *p, *s;
1048
1049         if(arg == 0)
1050                 arg = "";
1051
1052         if(debug)
1053                 logit("ls %s (. = %s)", arg, curdir);
1054
1055         /* process arguments, understand /bin/ls -l option */
1056         argv = alist;
1057         argv[0] = "/bin/ls";
1058         argc = getfields(arg, argv+1, Narg-2, 1, " \t") + 1;
1059         argv[argc] = 0;
1060         rflag = 0;
1061         tflag = 0;
1062         Rflag = 0;
1063         Cflag = 0;
1064         col = 0;
1065         ARGBEGIN{
1066         case 'l':
1067                 lflag++;
1068                 break;
1069         case 'R':
1070                 Rflag++;
1071                 break;
1072         case 'C':
1073                 Cflag++;
1074                 break;
1075         case 'r':
1076                 rflag++;
1077                 break;
1078         case 't':
1079                 tflag++;
1080                 break;
1081         }ARGEND;
1082         if(Cflag)
1083                 lflag = 0;
1084
1085         dfd = dialdata();
1086         if(dfd < 0){
1087                 reply("425 Error opening data connection: %r");
1088                 return;
1089         }
1090         reply("150 Opened data connection (%s)", data);
1091
1092         Binits(&bh, dfd, OWRITE, buf, sizeof(buf));
1093         if(argc == 0){
1094                 argc = 1;
1095                 argv = alist;
1096                 argv[0] = ".";
1097         }
1098
1099         for(i = 0; i < argc; i++){
1100                 chdir(curdir);
1101                 gl = glob(argv[i]);
1102                 if(gl == nil)
1103                         continue;
1104
1105                 printname = gl->first != nil && gl->first->next != nil;
1106                 maxnamelen = 8;
1107
1108                 if(Cflag)
1109                         for(g = gl->first; g; g = g->next)
1110                                 if(g->glob && (n = strlen(s_to_c(g->glob))) > maxnamelen)
1111                                         maxnamelen = n;
1112                 while(s = globiter(gl)){
1113                         if(debug)
1114                                 logit("glob %s", s);
1115                         p = abspath(s);
1116                         if(p == nil){
1117                                 free(s);
1118                                 continue;
1119                         }
1120                         d = dirstat(p);
1121                         if(d == nil){
1122                                 free(s);
1123                                 continue;
1124                         }
1125                         if(d->qid.type & QTDIR)
1126                                 listdir(s, &bh, lflag, &printname, gl);
1127                         else
1128                                 listfile(&bh, s, lflag, 0);
1129                         free(s);
1130                         free(d);
1131                 }
1132                 globlistfree(gl);
1133         }
1134         if(Cflag)
1135                 Bprint(&bh, "\r\n");
1136         Bflush(&bh);
1137         close(dfd);
1138
1139         reply("226 Transfer complete (list %s)", arg);
1140 }
1141 int
1142 namelistcmd(char *arg)
1143 {
1144         return asproc(list, arg, 0);
1145 }
1146 int
1147 listcmd(char *arg)
1148 {
1149         return asproc(list, arg, 1);
1150 }
1151
1152 /*
1153  * fuse compatability
1154  */
1155 int
1156 oksiteuser(void)
1157 {
1158         char buf[64];
1159         int fd, n;
1160
1161         fd = open("#c/user", OREAD);
1162         if(fd < 0)
1163                 return 1;
1164         n = read(fd, buf, sizeof buf - 1);
1165         if(n > 0){
1166                 buf[n] = 0;
1167                 if(strcmp(buf, "none") == 0)
1168                         n = -1;
1169         }
1170         close(fd);
1171         return n > 0;
1172 }
1173
1174 int
1175 sitecmd(char *arg)
1176 {
1177         char *f[4];
1178         int nf, r;
1179         Dir *d;
1180
1181         if(arg == 0)
1182                 return reply("501 bad site command");
1183         nf = tokenize(arg, f, nelem(f));
1184         if(nf != 3 || cistrcmp(f[0], "chmod") != 0)
1185                 return reply("501 bad site command");
1186         if(!oksiteuser())
1187                 return reply("550 Permission denied");
1188         d = dirstat(f[2]);
1189         if(d == nil)
1190                 return reply("501 site chmod: file does not exist");
1191         d->mode &= ~0777;
1192         d->mode |= strtoul(f[1], 0, 8) & 0777;
1193         r = dirwstat(f[2], d);
1194         free(d);
1195         if(r < 0)
1196                 return reply("550 Permission denied %r");
1197         return reply("200 very well, then");
1198  }
1199
1200 /*
1201  *  return the size of the file
1202  */
1203 int
1204 sizecmd(char *arg)
1205 {
1206         Dir *d;
1207         int rv;
1208
1209         if(arg == 0)
1210                 return reply("501 Size command requires pathname");
1211         arg = abspath(arg);
1212         d = dirstat(arg);
1213         if(d == nil)
1214                 return reply("501 %r accessing %s", arg);
1215         rv = reply("213 %lld", d->length);
1216         free(d);
1217         return rv;
1218 }
1219
1220 /*
1221  *  return the modify time of the file
1222  */
1223 int
1224 mdtmcmd(char *arg)
1225 {
1226         Dir *d;
1227         Tm *t;
1228         int rv;
1229
1230         if(arg == 0)
1231                 return reply("501 Mdtm command requires pathname");
1232         if(arg == 0)
1233                 return reply("550 Permission denied");
1234         d = dirstat(arg);
1235         if(d == nil)
1236                 return reply("501 %r accessing %s", arg);
1237         t = gmtime(d->mtime);
1238         rv = reply("213 %4.4d%2.2d%2.2d%2.2d%2.2d%2.2d",
1239                         t->year+1900, t->mon+1, t->mday,
1240                         t->hour, t->min, t->sec);
1241         free(d);
1242         return rv;
1243 }
1244
1245 /*
1246  *  set an offset to start reading a file from
1247  *  only lasts for one command
1248  */
1249 int
1250 restartcmd(char *arg)
1251 {
1252         if(arg == 0)
1253                 return reply("501 Restart command requires offset");
1254         offset = atoll(arg);
1255         if(offset < 0){
1256                 offset = 0;
1257                 return reply("501 Bad offset");
1258         }
1259
1260         return reply("350 Restarting at %lld. Send STORE or RETRIEVE", offset);
1261 }
1262
1263 /*
1264  *  send a file to the user
1265  */
1266 int
1267 crlfwrite(int fd, char *p, int n)
1268 {
1269         char *ep, *np;
1270         char buf[2*Nbuf];
1271
1272         for(np = buf, ep = p + n; p < ep; p++){
1273                 if(*p == '\n')
1274                         *np++ = '\r';
1275                 *np++ = *p;
1276         }
1277         if(write(fd, buf, np - buf) == np - buf)
1278                 return n;
1279         else
1280                 return -1;
1281 }
1282 void
1283 retrievedir(char *arg)
1284 {
1285         int n;
1286         char *p;
1287         String *file;
1288
1289         if(type != Timage){
1290                 reply("550 This file requires type binary/image");
1291                 return;
1292         }
1293
1294         file = s_copy(arg);
1295         p = strrchr(s_to_c(file), '/');
1296         if(p != s_to_c(file)){
1297                 *p++ = 0;
1298                 chdir(s_to_c(file));
1299         } else {
1300                 chdir("/");
1301                 p = s_to_c(file)+1;
1302         }
1303
1304         n = transfer("/bin/tar", "c", p, 0, 1);
1305         if(n < 0)
1306                 logit("get %s failed", arg);
1307         else
1308                 logit("get %s OK %d", arg, n);
1309         s_free(file);
1310 }
1311 void
1312 retrieve(char *arg, int arg2)
1313 {
1314         int dfd, fd, n, i, bytes;
1315         Dir *d;
1316         char buf[Nbuf];
1317         char *p, *ep;
1318
1319         USED(arg2);
1320
1321         p = strchr(arg, '\r');
1322         if(p){
1323                 logit("cr in file name", arg);
1324                 *p = 0;
1325         }
1326
1327         fd = open(arg, OREAD);
1328         if(fd == -1){
1329                 n = strlen(arg);
1330                 if(n > 4 && strcmp(arg+n-4, ".tar") == 0){
1331                         *(arg+n-4) = 0;
1332                         d = dirstat(arg);
1333                         if(d != nil){
1334                                 if(d->qid.type & QTDIR){
1335                                         retrievedir(arg);
1336                                         free(d);
1337                                         return;
1338                                 }
1339                                 free(d);
1340                         }
1341                 }
1342                 logit("get %s failed", arg);
1343                 reply("550 Error opening %s: %r", arg);
1344                 return;
1345         }
1346         if(offset != 0)
1347                 if(seek(fd, offset, 0) < 0){
1348                         reply("550 %s: seek to %lld failed", arg, offset);
1349                         close(fd);
1350                         return;
1351                 }
1352         d = dirfstat(fd);
1353         if(d != nil){
1354                 if(d->qid.type & QTDIR){
1355                         reply("550 %s: not a plain file.", arg);
1356                         close(fd);
1357                         free(d);
1358                         return;
1359                 }
1360                 free(d);
1361         }
1362
1363         n = read(fd, buf, sizeof(buf));
1364         if(n < 0){
1365                 logit("get %s failed", arg, mailaddr, nci->rsys);
1366                 reply("550 Error reading %s: %r", arg);
1367                 close(fd);
1368                 return;
1369         }
1370
1371         if(type != Timage)
1372                 for(p = buf, ep = &buf[n]; p < ep; p++)
1373                         if(*p & 0x80){
1374                                 close(fd);
1375                                 reply("550 This file requires type binary/image");
1376                                 return;
1377                         }
1378
1379         reply("150 Opening data connection for %s (%s)", arg, data);
1380         dfd = dialdata();
1381         if(dfd < 0){
1382                 reply("425 Error opening data connection: %r");
1383                 close(fd);
1384                 return;
1385         }
1386
1387         bytes = 0;
1388         do {
1389                 switch(type){
1390                 case Timage:
1391                         i = write(dfd, buf, n);
1392                         break;
1393                 default:
1394                         i = crlfwrite(dfd, buf, n);
1395                         break;
1396                 }
1397                 if(i != n){
1398                         close(fd);
1399                         close(dfd);
1400                         logit("get %s %r to data connection after %d", arg, bytes);
1401                         reply("550 Error writing to data connection: %r");
1402                         return;
1403                 }
1404                 bytes += n;
1405         } while((n = read(fd, buf, sizeof(buf))) > 0);
1406
1407         if(n < 0)
1408                 logit("get %s %r after %d", arg, bytes);
1409
1410         close(fd);
1411         close(dfd);
1412         reply("226 Transfer complete");
1413         logit("get %s OK %d", arg, bytes);
1414 }
1415 int
1416 retrievecmd(char *arg)
1417 {
1418         if(arg == 0)
1419                 return reply("501 Retrieve command requires an argument");
1420         arg = abspath(arg);
1421         if(arg == 0)
1422                 return reply("550 Permission denied");
1423
1424         return asproc(retrieve, arg, 0);
1425 }
1426
1427 /*
1428  *  get a file from the user
1429  */
1430 int
1431 lfwrite(int fd, char *p, int n)
1432 {
1433         char *ep, *np;
1434         char buf[Nbuf];
1435
1436         for(np = buf, ep = p + n; p < ep; p++){
1437                 if(*p != '\r')
1438                         *np++ = *p;
1439         }
1440         if(write(fd, buf, np - buf) == np - buf)
1441                 return n;
1442         else
1443                 return -1;
1444 }
1445 void
1446 store(char *arg, int fd)
1447 {
1448         int dfd, n, i;
1449         char buf[Nbuf];
1450
1451         reply("150 Opening data connection for %s (%s)", arg, data);
1452         dfd = dialdata();
1453         if(dfd < 0){
1454                 reply("425 Error opening data connection: %r");
1455                 close(fd);
1456                 return;
1457         }
1458
1459         while((n = read(dfd, buf, sizeof(buf))) > 0){
1460                 switch(type){
1461                 case Timage:
1462                         i = write(fd, buf, n);
1463                         break;
1464                 default:
1465                         i = lfwrite(fd, buf, n);
1466                         break;
1467                 }
1468                 if(i != n){
1469                         close(fd);
1470                         close(dfd);
1471                         reply("550 Error writing file");
1472                         return;
1473                 }
1474         }
1475         close(fd);
1476         close(dfd);
1477         logit("put %s OK", arg);
1478         reply("226 Transfer complete");
1479 }
1480 int
1481 storecmd(char *arg)
1482 {
1483         int fd, rv;
1484
1485         if(arg == 0)
1486                 return reply("501 Store command requires an argument");
1487         arg = abspath(arg);
1488         if(arg == 0)
1489                 return reply("550 Permission denied");
1490         if(isnone && strncmp(arg, "/incoming/", sizeof("/incoming/")-1))
1491                 return reply("550 Permission denied");
1492         if(offset){
1493                 fd = open(arg, OWRITE);
1494                 if(fd == -1)
1495                         return reply("550 Error opening %s: %r", arg);
1496                 if(seek(fd, offset, 0) == -1)
1497                         return reply("550 Error seeking %s to %d: %r",
1498                                 arg, offset);
1499         } else {
1500                 fd = create(arg, OWRITE, createperm);
1501                 if(fd == -1)
1502                         return reply("550 Error creating %s: %r", arg);
1503         }
1504
1505         rv = asproc(store, arg, fd);
1506         close(fd);
1507         return rv;
1508 }
1509 int
1510 appendcmd(char *arg)
1511 {
1512         int fd, rv;
1513
1514         if(arg == 0)
1515                 return reply("501 Append command requires an argument");
1516         if(isnone)
1517                 return reply("550 Permission denied");
1518         arg = abspath(arg);
1519         if(arg == 0)
1520                 return reply("550 Error creating %s: Permission denied", arg);
1521         fd = open(arg, OWRITE);
1522         if(fd == -1){
1523                 fd = create(arg, OWRITE, createperm);
1524                 if(fd == -1)
1525                         return reply("550 Error creating %s: %r", arg);
1526         }
1527         seek(fd, 0, 2);
1528
1529         rv = asproc(store, arg, fd);
1530         close(fd);
1531         return rv;
1532 }
1533 int
1534 storeucmd(char *arg)
1535 {
1536         int fd, rv;
1537         char name[Maxpath];
1538
1539         USED(arg);
1540         if(isnone)
1541                 return reply("550 Permission denied");
1542         strncpy(name, "ftpXXXXXXXXXXX", sizeof name);
1543         mktemp(name);
1544         fd = create(name, OWRITE, createperm);
1545         if(fd == -1)
1546                 return reply("550 Error creating %s: %r", name);
1547
1548         rv = asproc(store, name, fd);
1549         close(fd);
1550         return rv;
1551 }
1552
1553 int
1554 mkdircmd(char *name)
1555 {
1556         int fd;
1557
1558         if(name == 0)
1559                 return reply("501 Mkdir command requires an argument");
1560         if(isnone)
1561                 return reply("550 Permission denied");
1562         name = abspath(name);
1563         if(name == 0)
1564                 return reply("550 Permission denied");
1565         fd = create(name, OREAD, DMDIR|0775);
1566         if(fd < 0)
1567                 return reply("550 Can't create %s: %r", name);
1568         close(fd);
1569         return reply("226 %s created", name);
1570 }
1571
1572 int
1573 delcmd(char *name)
1574 {
1575         if(name == 0)
1576                 return reply("501 Rmdir/delete command requires an argument");
1577         if(isnone)
1578                 return reply("550 Permission denied");
1579         name = abspath(name);
1580         if(name == 0)
1581                 return reply("550 Permission denied");
1582         if(remove(name) < 0)
1583                 return reply("550 Can't remove %s: %r", name);
1584         else
1585                 return reply("226 %s removed", name);
1586 }
1587
1588 /*
1589  *  kill off the last transfer (if the process still exists)
1590  */
1591 int
1592 abortcmd(char *arg)
1593 {
1594         USED(arg);
1595
1596         logit("abort pid %d", pid);
1597         if(pid){
1598                 if(postnote(PNPROC, pid, "kill") == 0)
1599                         reply("426 Command aborted");
1600                 else
1601                         logit("postnote pid %d %r", pid);
1602         }
1603         return reply("226 Abort processed");
1604 }
1605
1606 int
1607 systemcmd(char *arg)
1608 {
1609         USED(arg);
1610         return reply("215 UNIX Type: L8 Version: Plan 9");
1611 }
1612
1613 int
1614 helpcmd(char *arg)
1615 {
1616         int i;
1617         char buf[80];
1618         char *p, *e;
1619
1620         USED(arg);
1621         reply("214- the following commands are implemented:");
1622         buf[0] = 0;
1623         p = buf;
1624         e = buf+sizeof buf;
1625         for(i = 0; cmdtab[i].name; i++){
1626                 if((i%8) == 0){
1627                         reply("214-%s", buf);
1628                         p = buf;
1629                 }
1630                 p = seprint(p, e, " %-5.5s", cmdtab[i].name);
1631         }
1632         if(p != buf)
1633                 reply("214-%s", buf);
1634         reply("214 ");
1635         return 0;
1636 }
1637
1638 /*
1639  *  renaming a file takes two commands
1640  */
1641 static String *filepath;
1642
1643 int
1644 rnfrcmd(char *from)
1645 {
1646         if(isnone)
1647                 return reply("550 Permission denied");
1648         if(from == 0)
1649                 return reply("501 Rename command requires an argument");
1650         from = abspath(from);
1651         if(from == 0)
1652                 return reply("550 Permission denied");
1653         if(filepath == nil)
1654                 filepath = s_copy(from);
1655         else{
1656                 s_reset(filepath);
1657                 s_append(filepath, from);
1658         }
1659         return reply("350 Rename %s to ...", s_to_c(filepath));
1660 }
1661 int
1662 rntocmd(char *to)
1663 {
1664         int r;
1665         Dir nd;
1666         char *fp, *tp;
1667
1668         if(isnone)
1669                 return reply("550 Permission denied");
1670         if(to == 0)
1671                 return reply("501 Rename command requires an argument");
1672         to = abspath(to);
1673         if(to == 0)
1674                 return reply("550 Permission denied");
1675         if(filepath == nil || *(s_to_c(filepath)) == 0)
1676                 return reply("503 Rnto must be preceeded by an rnfr");
1677
1678         tp = strrchr(to, '/');
1679         fp = strrchr(s_to_c(filepath), '/');
1680         if((tp && fp == 0) || (fp && tp == 0)
1681         || (fp && tp && (fp-s_to_c(filepath) != tp-to || memcmp(s_to_c(filepath), to, tp-to))))
1682                 return reply("550 Rename can't change directory");
1683         if(tp)
1684                 to = tp+1;
1685
1686         nulldir(&nd);
1687         nd.name = to;
1688         if(dirwstat(s_to_c(filepath), &nd) < 0)
1689                 r = reply("550 Can't rename %s to %s: %r\n", s_to_c(filepath), to);
1690         else
1691                 r = reply("250 %s now %s", s_to_c(filepath), to);
1692         s_reset(filepath);
1693
1694         return r;
1695 }
1696
1697 /*
1698  *  to dial out we need the network file system in our
1699  *  name space.
1700  */
1701 int
1702 dialdata(void)
1703 {
1704         int fd, cfd;
1705         char ldir[40];
1706         char err[ERRMAX];
1707
1708         if(mountnet() < 0)
1709                 return -1;
1710
1711         if(!passive.inuse)
1712                 fd = dial(data, "20", 0, 0);
1713         else {
1714                 fd = -1;
1715                 alarm(5*60*1000);
1716                 cfd = listen(passive.adir, ldir);
1717                 alarm(0);
1718                 if(cfd >= 0){
1719                         fd = accept(cfd, ldir);
1720                         close(cfd);
1721                 }
1722         }
1723         err[0] = 0;
1724         errstr(err, sizeof err);
1725         if(fd < 0)
1726                 logit("can't dial %s: %s", data, err);
1727         unmountnet();
1728         errstr(err, sizeof err);
1729         return fd;
1730 }
1731
1732 int
1733 postnote(int group, int pid, char *note)
1734 {
1735         char file[128];
1736         int f, r;
1737
1738         /*
1739          * Use #p because /proc may not be in the namespace.
1740          */
1741         switch(group) {
1742         case PNPROC:
1743                 sprint(file, "#p/%d/note", pid);
1744                 break;
1745         case PNGROUP:
1746                 sprint(file, "#p/%d/notepg", pid);
1747                 break;
1748         default:
1749                 return -1;
1750         }
1751
1752         f = open(file, OWRITE);
1753         if(f < 0)
1754                 return -1;
1755
1756         r = strlen(note);
1757         if(write(f, note, r) != r) {
1758                 close(f);
1759                 return -1;
1760         }
1761         close(f);
1762         return 0;
1763 }
1764
1765 /*
1766  *  to circumscribe the accessible files we have to eliminate ..'s
1767  *  and resolve all names from the root.  We also remove any /bin/rc
1768  *  special characters to avoid later problems with executed commands.
1769  */
1770 char *special = "`;| ";
1771
1772 char*
1773 abspath(char *origpath)
1774 {
1775         char *p, *sp, *path;
1776         static String *rpath;
1777
1778         if(rpath == nil)
1779                 rpath = s_new();
1780         else
1781                 s_reset(rpath);
1782
1783         if(origpath == nil)
1784                 s_append(rpath, curdir);
1785         else{
1786                 if(*origpath != '/'){
1787                         s_append(rpath, curdir);
1788                         s_append(rpath, "/");
1789                 }
1790                 s_append(rpath, origpath);
1791         }
1792         path = s_to_c(rpath);
1793
1794         for(sp = special; *sp; sp++){
1795                 p = strchr(path, *sp);
1796                 if(p)
1797                         *p = 0;
1798         }
1799
1800         cleanname(s_to_c(rpath));
1801         rpath->ptr = rpath->base+strlen(rpath->base);
1802
1803         if(!accessok(s_to_c(rpath)))
1804                 return nil;
1805
1806         return s_to_c(rpath);
1807 }
1808
1809 typedef struct Path Path;
1810 struct Path {
1811         Path    *next;
1812         String  *path;
1813         int     inuse;
1814         int     ok;
1815 };
1816
1817 enum
1818 {
1819         Maxlevel = 16,
1820         Maxperlevel= 8,
1821 };
1822
1823 Path *pathlevel[Maxlevel];
1824
1825 Path*
1826 unlinkpath(char *path, int level)
1827 {
1828         String *s;
1829         Path **l, *p;
1830         int n;
1831
1832         n = 0;
1833         for(l = &pathlevel[level]; *l; l = &(*l)->next){
1834                 p = *l;
1835                 /* hit */
1836                 if(strcmp(s_to_c(p->path), path) == 0){
1837                         *l = p->next;
1838                         p->next = nil;
1839                         return p;
1840                 }
1841                 /* reuse */
1842                 if(++n >= Maxperlevel){
1843                         *l = p->next;
1844                         s = p->path;
1845                         s_reset(p->path);
1846                         memset(p, 0, sizeof *p);
1847                         p->path = s_append(s, path);
1848                         return p;
1849                 }
1850         }
1851
1852         /* allocate */
1853         p = mallocz(sizeof *p, 1);
1854         p->path = s_copy(path);
1855         return p;
1856 }
1857
1858 void
1859 linkpath(Path *p, int level)
1860 {
1861         p->next = pathlevel[level];
1862         pathlevel[level] = p;
1863         p->inuse = 1;
1864 }
1865
1866 void
1867 addpath(Path *p, int level, int ok)
1868 {
1869         p->ok = ok;
1870         p->next = pathlevel[level];
1871         pathlevel[level] = p;
1872 }
1873
1874 int
1875 _accessok(String *s, int level)
1876 {
1877         Path *p;
1878         char *cp;
1879         int lvl, offset;
1880         static char httplogin[] = "/.httplogin";
1881
1882         if(level < 0)
1883                 return 1;
1884         lvl = level;
1885         if(lvl >= Maxlevel)
1886                 lvl = Maxlevel - 1;
1887
1888         p = unlinkpath(s_to_c(s), lvl);
1889         if(p->inuse){
1890                 /* move to front */
1891                 linkpath(p, lvl);
1892                 return p->ok;
1893         }
1894         cp = strrchr(s_to_c(s), '/');
1895         if(cp == nil)
1896                 offset = 0;
1897         else
1898                 offset = cp - s_to_c(s);
1899         s_append(s, httplogin);
1900         if(access(s_to_c(s), AEXIST) == 0){
1901                 addpath(p, lvl, 0);
1902                 return 0;
1903         }
1904
1905         /*
1906          * There's no way to shorten a String without
1907          * knowing the implementation.
1908          */
1909         s->ptr = s->base+offset;
1910         s_terminate(s);
1911         addpath(p, lvl, _accessok(s, level-1));
1912
1913         return p->ok;
1914 }
1915
1916 /*
1917  * check for a subdirectory containing .httplogin
1918  * at each level of the path.
1919  */
1920 int
1921 accessok(char *path)
1922 {
1923         int level, r;
1924         char *p;
1925         String *npath;
1926
1927         npath = s_copy(path);
1928         p = s_to_c(npath)+1;
1929         for(level = 1; level < Maxlevel; level++){
1930                 p = strchr(p, '/');
1931                 if(p == nil)
1932                         break;
1933                 p++;
1934         }
1935
1936         r = _accessok(npath, level-1);
1937         s_free(npath);
1938
1939         return r;
1940 }