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