]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/ftpd.c
ip/ipconfig: default onlink and autoflag to 1
[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) < 0
416                         || amount(fd, "/bin", MREPL, "") < 0
417                         || bind("#c", "/dev", MAFTER) < 0
418                         || bind(bindir, "/bin", MREPL) < 0)
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
610         if(response == nil)
611                 response = "";
612
613         if(strcmp(user, "none") == 0 || strcmp(user, "*none") == 0){
614                 /* for none, accept anything as a password */
615                 isnone = 1;
616                 strncpy(mailaddr, response, sizeof(mailaddr)-1);
617                 return loginuser("none", namespace, 1);
618         }
619
620         if(isnoworld){
621                 /* noworld gets a password in the clear */
622                 if(login(user, response, "/lib/namespace.noworld") < 0)
623                         return reply("530 Not logged in");
624                 createperm = 0664;
625                 /* login has already setup the namespace */
626                 return loginuser(user, nil, 0);
627         } else {
628                 /* for everyone else, do challenge response */
629                 if(ch == nil)
630                         return reply("531 Send user id before encrypted challenge");
631                 ch->resp = response;
632                 ch->nresp = strlen(response);
633                 ai = auth_response(ch);
634                 if(ai == nil || auth_chuid(ai, nil) < 0) {
635                         slowdown();
636                         return reply("530 Not logged in: %r");
637                 }
638                 auth_freechal(ch);
639                 ch = nil;
640
641                 /* if the user has specified a namespace for ftp, use it */
642                 snprint(namefile, sizeof(namefile), "/usr/%s/lib/namespace.ftp", user);
643                 strcpy(mailaddr, user);
644                 createperm = 0660;
645                 if(access(namefile, 0) == 0)
646                         return loginuser(user, namefile, 0);
647                 else
648                         return loginuser(user, "/lib/namespace", 0);
649         }
650 }
651
652 /*
653  *  print working directory
654  */
655 int
656 pwdcmd(char *arg)
657 {
658         if(arg)
659                 return reply("550 Pwd takes no argument");
660         return reply("257 \"%s\" is the current directory", curdir);
661 }
662
663 /*
664  *  chdir
665  */
666 int
667 cwdcmd(char *dir)
668 {
669         char *rp;
670         char buf[Maxpath];
671
672         /* shell cd semantics */
673         if(dir == 0 || *dir == 0){
674                 if(isnone)
675                         rp = "/";
676                 else {
677                         snprint(buf, sizeof buf, "/usr/%s", user);
678                         rp = buf;
679                 }
680                 if(accessok(rp) == 0)
681                         rp = nil;
682         } else
683                 rp = abspath(dir);
684
685         if(rp == nil)
686                 return reply("550 Permission denied");
687
688         if(chdir(rp) < 0)
689                 return reply("550 Cwd failed: %r");
690         strcpy(curdir, rp);
691         return reply("250 directory changed to %s", curdir);
692 }
693
694 /*
695  *  chdir ..
696  */
697 int
698 cdupcmd(char *dp)
699 {
700         USED(dp);
701         return cwdcmd("..");
702 }
703
704 int
705 quitcmd(char *arg)
706 {
707         USED(arg);
708         reply("200 Bye");
709         if(pid)
710                 postnote(PNPROC, pid, "kill");
711         return -1;
712 }
713
714 int
715 typecmd(char *arg)
716 {
717         int c;
718         char *x;
719
720         x = arg;
721         if(arg == 0)
722                 return reply("501 Type command needs arguments");
723
724         while(c = *arg++){
725                 switch(tolower(c)){
726                 case 'a':
727                         type = Tascii;
728                         break;
729                 case 'i':
730                 case 'l':
731                         type = Timage;
732                         break;
733                 case '8':
734                 case ' ':
735                 case 'n':
736                 case 't':
737                 case 'c':
738                         break;
739                 default:
740                         return reply("501 Unimplemented type %s", x);
741                 }
742         }
743         return reply("200 Type %s", type==Tascii ? "Ascii" : "Image");
744 }
745
746 int
747 modecmd(char *arg)
748 {
749         if(arg == 0)
750                 return reply("501 Mode command needs arguments");
751         while(*arg){
752                 switch(tolower(*arg)){
753                 case 's':
754                         mode = Mstream;
755                         break;
756                 default:
757                         return reply("501 Unimplemented mode %c", *arg);
758                 }
759                 arg++;
760         }
761         return reply("200 Stream mode");
762 }
763
764 int
765 structcmd(char *arg)
766 {
767         if(arg == 0)
768                 return reply("501 Struct command needs arguments");
769         for(; *arg; arg++){
770                 switch(tolower(*arg)){
771                 case 'f':
772                         structure = Sfile;
773                         break;
774                 default:
775                         return reply("501 Unimplemented structure %c", *arg);
776                 }
777         }
778         return reply("200 File structure");
779 }
780
781 int
782 portcmd(char *arg)
783 {
784         char *field[7];
785         int n;
786
787         if(arg == 0)
788                 return reply("501 Port command needs arguments");
789         n = getfields(arg, field, 7, 0, ", ");
790         if(n != 6)
791                 return reply("501 Incorrect port specification");
792         snprint(data, sizeof data, "tcp!%.3s.%.3s.%.3s.%.3s!%d", field[0], field[1], field[2],
793                 field[3], atoi(field[4])*256 + atoi(field[5]));
794         return reply("200 Data port is %s", data);
795 }
796
797 int
798 mountnet(void)
799 {
800         int rv;
801
802         rv = 0;
803
804         if(bind("#/", "/", MAFTER) < 0){
805                 logit("can't bind #/ to /: %r");
806                 return reply("500 can't bind #/ to /: %r");
807         }
808
809         if(bind(nci->spec, "/net", MBEFORE) < 0){
810                 logit("can't bind %s to /net: %r", nci->spec);
811                 rv = reply("500 can't bind %s to /net: %r", nci->spec);
812                 unmount("#/", "/");
813         }
814
815         return rv;
816 }
817
818 void
819 unmountnet(void)
820 {
821         unmount(0, "/net");
822         unmount("#/", "/");
823 }
824
825 int
826 pasvcmd(char *arg)
827 {
828         NetConnInfo *nnci;
829         Passive *p;
830
831         USED(arg);
832         p = &passive;
833
834         if(p->inuse){
835                 close(p->afd);
836                 p->inuse = 0;
837         }
838
839         if(mountnet() < 0)
840                 return 0;
841
842         p->afd = announce("tcp!*!0", passive.adir);
843         if(p->afd < 0){
844                 unmountnet();
845                 return reply("500 No free ports");
846         }
847         nnci = getnetconninfo(p->adir, -1);
848         unmountnet();
849
850         /* parse the local address */
851         if(debug)
852                 logit("local sys is %s", nci->lsys);
853         parseip(p->ipaddr, nci->lsys);
854         if(ipcmp(p->ipaddr, v4prefix) == 0 || ipcmp(p->ipaddr, IPnoaddr) == 0)
855                 parseip(p->ipaddr, nci->lsys);
856         p->port = atoi(nnci->lserv);
857
858         freenetconninfo(nnci);
859         p->inuse = 1;
860
861         return reply("227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)",
862                 p->ipaddr[IPv4off+0], p->ipaddr[IPv4off+1], p->ipaddr[IPv4off+2], p->ipaddr[IPv4off+3],
863                 p->port>>8, p->port&0xff);
864 }
865
866 enum
867 {
868         Narg=32,
869 };
870 int Cflag, rflag, tflag, Rflag;
871 int maxnamelen;
872 int col;
873
874 char*
875 mode2asc(int m)
876 {
877         static char asc[12];
878         char *p;
879
880         strcpy(asc, "----------");
881         if(DMDIR & m)
882                 asc[0] = 'd';
883         if(DMAPPEND & m)
884                 asc[0] = 'a';
885         else if(DMEXCL & m)
886                 asc[3] = 'l';
887
888         for(p = asc+1; p < asc + 10; p += 3, m<<=3){
889                 if(m & 0400)
890                         p[0] = 'r';
891                 if(m & 0200)
892                         p[1] = 'w';
893                 if(m & 0100)
894                         p[2] = 'x';
895         }
896         return asc;
897 }
898 void
899 listfile(Biobufhdr *b, char *name, int lflag, char *dname)
900 {
901         char ts[32];
902         int n, links, pad;
903         long now;
904         char *x;
905         Dir *d;
906
907         x = abspath(name);
908         if(x == nil)
909                 return;
910         d = dirstat(x);
911         if(d == nil)
912                 return;
913         if(isnone){
914                 if(strncmp(x, "/incoming/", sizeof("/incoming/")-1) != 0)
915                         d->mode &= ~0222;
916                 d->uid = "none";
917                 d->gid = "none";
918         }
919
920         strcpy(ts, ctime(d->mtime));
921         ts[16] = 0;
922         now = time(0);
923         if(now - d->mtime > 6*30*24*60*60)
924                 memmove(ts+11, ts+23, 5);
925         if(lflag){
926                 /* Unix style long listing */
927                 if(DMDIR&d->mode){
928                         links = 2;
929                         d->length = 512;
930                 } else
931                         links = 1;
932
933                 Bprint(b, "%s %3d %-8s %-8s %7lld %s ",
934                         mode2asc(d->mode), links,
935                         d->uid, d->gid, d->length, ts+4);
936         }
937         if(Cflag && maxnamelen < 40){
938                 n = strlen(name);
939                 pad = ((col+maxnamelen)/(maxnamelen+1))*(maxnamelen+1);
940                 if(pad+maxnamelen+1 < 60){
941                         Bprint(b, "%*s", pad-col+n, name);
942                         col = pad+n;
943                 }
944                 else{
945                         Bprint(b, "\r\n%s", name);
946                         col = n;
947                 }
948         }
949         else{
950                 if(dname)
951                         Bprint(b, "%s/", dname);
952                 Bprint(b, "%s\r\n", name);
953         }
954         free(d);
955 }
956 int
957 dircomp(void *va, void *vb)
958 {
959         int rv;
960         Dir *a, *b;
961
962         a = va;
963         b = vb;
964
965         if(tflag)
966                 rv = b->mtime - a->mtime;
967         else
968                 rv = strcmp(a->name, b->name);
969         return (rflag?-1:1)*rv;
970 }
971 void
972 listdir(char *name, Biobufhdr *b, int lflag, int *printname, Globlist *gl)
973 {
974         Dir *p;
975         int fd, n, i, l;
976         char *dname;
977         uvlong total;
978
979         col = 0;
980
981         fd = open(name, OREAD);
982         if(fd < 0){
983                 Bprint(b, "can't read %s: %r\r\n", name);
984                 return;
985         }
986         dname = 0;
987         if(*printname){
988                 if(Rflag || lflag)
989                         Bprint(b, "\r\n%s:\r\n", name);
990                 else
991                         dname = name;
992         }
993         n = dirreadall(fd, &p);
994         close(fd);
995         if(Cflag){
996                 for(i = 0; i < n; i++){
997                         l = strlen(p[i].name);
998                         if(l > maxnamelen)
999                                 maxnamelen = l;
1000                 }
1001         }
1002
1003         /* Unix style total line */
1004         if(lflag){
1005                 total = 0;
1006                 for(i = 0; i < n; i++){
1007                         if(p[i].qid.type & QTDIR)
1008                                 total += 512;
1009                         else
1010                                 total += p[i].length;
1011                 }
1012                 Bprint(b, "total %ulld\r\n", total/512);
1013         }
1014
1015         qsort(p, n, sizeof(Dir), dircomp);
1016         for(i = 0; i < n; i++){
1017                 if(Rflag && (p[i].qid.type & QTDIR)){
1018                         *printname = 1;
1019                         globadd(gl, name, p[i].name);
1020                 }
1021                 listfile(b, p[i].name, lflag, dname);
1022         }
1023         free(p);
1024 }
1025 void
1026 list(char *arg, int lflag)
1027 {
1028         Dir *d;
1029         Globlist *gl;
1030         Glob *g;
1031         int dfd, printname;
1032         int i, n, argc;
1033         char *alist[Narg];
1034         char **argv;
1035         Biobufhdr bh;
1036         uchar buf[512];
1037         char *p, *s;
1038
1039         if(arg == 0)
1040                 arg = "";
1041
1042         if(debug)
1043                 logit("ls %s (. = %s)", arg, curdir);
1044
1045         /* process arguments, understand /bin/ls -l option */
1046         argv = alist;
1047         argv[0] = "/bin/ls";
1048         argc = getfields(arg, argv+1, Narg-2, 1, " \t") + 1;
1049         argv[argc] = 0;
1050         rflag = 0;
1051         tflag = 0;
1052         Rflag = 0;
1053         Cflag = 0;
1054         col = 0;
1055         ARGBEGIN{
1056         case 'l':
1057                 lflag++;
1058                 break;
1059         case 'R':
1060                 Rflag++;
1061                 break;
1062         case 'C':
1063                 Cflag++;
1064                 break;
1065         case 'r':
1066                 rflag++;
1067                 break;
1068         case 't':
1069                 tflag++;
1070                 break;
1071         }ARGEND;
1072         if(Cflag)
1073                 lflag = 0;
1074
1075         dfd = dialdata();
1076         if(dfd < 0){
1077                 reply("425 Error opening data connection: %r");
1078                 return;
1079         }
1080         reply("150 Opened data connection (%s)", data);
1081
1082         Binits(&bh, dfd, OWRITE, buf, sizeof(buf));
1083         if(argc == 0){
1084                 argc = 1;
1085                 argv = alist;
1086                 argv[0] = ".";
1087         }
1088
1089         for(i = 0; i < argc; i++){
1090                 chdir(curdir);
1091                 gl = glob(argv[i]);
1092                 if(gl == nil)
1093                         continue;
1094
1095                 printname = gl->first != nil && gl->first->next != nil;
1096                 maxnamelen = 8;
1097
1098                 if(Cflag)
1099                         for(g = gl->first; g; g = g->next)
1100                                 if(g->glob && (n = strlen(s_to_c(g->glob))) > maxnamelen)
1101                                         maxnamelen = n;
1102                 while(s = globiter(gl)){
1103                         if(debug)
1104                                 logit("glob %s", s);
1105                         p = abspath(s);
1106                         if(p == nil){
1107                                 free(s);
1108                                 continue;
1109                         }
1110                         d = dirstat(p);
1111                         if(d == nil){
1112                                 free(s);
1113                                 continue;
1114                         }
1115                         if(d->qid.type & QTDIR)
1116                                 listdir(s, &bh, lflag, &printname, gl);
1117                         else
1118                                 listfile(&bh, s, lflag, 0);
1119                         free(s);
1120                         free(d);
1121                 }
1122                 globlistfree(gl);
1123         }
1124         if(Cflag)
1125                 Bprint(&bh, "\r\n");
1126         Bflush(&bh);
1127         close(dfd);
1128
1129         reply("226 Transfer complete (list %s)", arg);
1130 }
1131 int
1132 namelistcmd(char *arg)
1133 {
1134         return asproc(list, arg, 0);
1135 }
1136 int
1137 listcmd(char *arg)
1138 {
1139         return asproc(list, arg, 1);
1140 }
1141
1142 /*
1143  * fuse compatability
1144  */
1145 int
1146 oksiteuser(void)
1147 {
1148         char buf[64];
1149         int fd, n;
1150
1151         fd = open("#c/user", OREAD);
1152         if(fd < 0)
1153                 return 1;
1154         n = read(fd, buf, sizeof buf - 1);
1155         if(n > 0){
1156                 buf[n] = 0;
1157                 if(strcmp(buf, "none") == 0)
1158                         n = -1;
1159         }
1160         close(fd);
1161         return n > 0;
1162 }
1163
1164 int
1165 sitecmd(char *arg)
1166 {
1167         char *f[4];
1168         int nf, r;
1169         Dir *d;
1170
1171         if(arg == 0)
1172                 return reply("501 bad site command");
1173         nf = tokenize(arg, f, nelem(f));
1174         if(nf != 3 || cistrcmp(f[0], "chmod") != 0)
1175                 return reply("501 bad site command");
1176         if(!oksiteuser())
1177                 return reply("550 Permission denied");
1178         d = dirstat(f[2]);
1179         if(d == nil)
1180                 return reply("501 site chmod: file does not exist");
1181         d->mode &= ~0777;
1182         d->mode |= strtoul(f[1], 0, 8) & 0777;
1183         r = dirwstat(f[2], d);
1184         free(d);
1185         if(r < 0)
1186                 return reply("550 Permission denied %r");
1187         return reply("200 very well, then");
1188  }
1189
1190 /*
1191  *  return the size of the file
1192  */
1193 int
1194 sizecmd(char *arg)
1195 {
1196         Dir *d;
1197         int rv;
1198
1199         if(arg == 0)
1200                 return reply("501 Size command requires pathname");
1201         arg = abspath(arg);
1202         d = dirstat(arg);
1203         if(d == nil)
1204                 return reply("501 %r accessing %s", arg);
1205         rv = reply("213 %lld", d->length);
1206         free(d);
1207         return rv;
1208 }
1209
1210 /*
1211  *  return the modify time of the file
1212  */
1213 int
1214 mdtmcmd(char *arg)
1215 {
1216         Dir *d;
1217         Tm *t;
1218         int rv;
1219
1220         if(arg == 0)
1221                 return reply("501 Mdtm command requires pathname");
1222         if(arg == 0)
1223                 return reply("550 Permission denied");
1224         d = dirstat(arg);
1225         if(d == nil)
1226                 return reply("501 %r accessing %s", arg);
1227         t = gmtime(d->mtime);
1228         rv = reply("213 %4.4d%2.2d%2.2d%2.2d%2.2d%2.2d",
1229                         t->year+1900, t->mon+1, t->mday,
1230                         t->hour, t->min, t->sec);
1231         free(d);
1232         return rv;
1233 }
1234
1235 /*
1236  *  set an offset to start reading a file from
1237  *  only lasts for one command
1238  */
1239 int
1240 restartcmd(char *arg)
1241 {
1242         if(arg == 0)
1243                 return reply("501 Restart command requires offset");
1244         offset = atoll(arg);
1245         if(offset < 0){
1246                 offset = 0;
1247                 return reply("501 Bad offset");
1248         }
1249
1250         return reply("350 Restarting at %lld. Send STORE or RETRIEVE", offset);
1251 }
1252
1253 /*
1254  *  send a file to the user
1255  */
1256 int
1257 crlfwrite(int fd, char *p, int n)
1258 {
1259         char *ep, *np;
1260         char buf[2*Nbuf];
1261
1262         for(np = buf, ep = p + n; p < ep; p++){
1263                 if(*p == '\n')
1264                         *np++ = '\r';
1265                 *np++ = *p;
1266         }
1267         if(write(fd, buf, np - buf) == np - buf)
1268                 return n;
1269         else
1270                 return -1;
1271 }
1272 void
1273 retrievedir(char *arg)
1274 {
1275         int n;
1276         char *p;
1277         String *file;
1278
1279         if(type != Timage){
1280                 reply("550 This file requires type binary/image");
1281                 return;
1282         }
1283
1284         file = s_copy(arg);
1285         p = strrchr(s_to_c(file), '/');
1286         if(p != s_to_c(file)){
1287                 *p++ = 0;
1288                 chdir(s_to_c(file));
1289         } else {
1290                 chdir("/");
1291                 p = s_to_c(file)+1;
1292         }
1293
1294         n = transfer("/bin/tar", "c", p, 0, 1);
1295         if(n < 0)
1296                 logit("get %s failed", arg);
1297         else
1298                 logit("get %s OK %d", arg, n);
1299         s_free(file);
1300 }
1301 void
1302 retrieve(char *arg, int arg2)
1303 {
1304         int dfd, fd, n, i, bytes;
1305         Dir *d;
1306         char buf[Nbuf];
1307         char *p, *ep;
1308
1309         USED(arg2);
1310
1311         p = strchr(arg, '\r');
1312         if(p){
1313                 logit("cr in file name", arg);
1314                 *p = 0;
1315         }
1316
1317         fd = open(arg, OREAD);
1318         if(fd == -1){
1319                 n = strlen(arg);
1320                 if(n > 4 && strcmp(arg+n-4, ".tar") == 0){
1321                         *(arg+n-4) = 0;
1322                         d = dirstat(arg);
1323                         if(d != nil){
1324                                 if(d->qid.type & QTDIR){
1325                                         retrievedir(arg);
1326                                         free(d);
1327                                         return;
1328                                 }
1329                                 free(d);
1330                         }
1331                 }
1332                 logit("get %s failed", arg);
1333                 reply("550 Error opening %s: %r", arg);
1334                 return;
1335         }
1336         if(offset != 0)
1337                 if(seek(fd, offset, 0) < 0){
1338                         reply("550 %s: seek to %lld failed", arg, offset);
1339                         close(fd);
1340                         return;
1341                 }
1342         d = dirfstat(fd);
1343         if(d != nil){
1344                 if(d->qid.type & QTDIR){
1345                         reply("550 %s: not a plain file.", arg);
1346                         close(fd);
1347                         free(d);
1348                         return;
1349                 }
1350                 free(d);
1351         }
1352
1353         n = read(fd, buf, sizeof(buf));
1354         if(n < 0){
1355                 logit("get %s failed", arg, mailaddr, nci->rsys);
1356                 reply("550 Error reading %s: %r", arg);
1357                 close(fd);
1358                 return;
1359         }
1360
1361         if(type != Timage)
1362                 for(p = buf, ep = &buf[n]; p < ep; p++)
1363                         if(*p & 0x80){
1364                                 close(fd);
1365                                 reply("550 This file requires type binary/image");
1366                                 return;
1367                         }
1368
1369         reply("150 Opening data connection for %s (%s)", arg, data);
1370         dfd = dialdata();
1371         if(dfd < 0){
1372                 reply("425 Error opening data connection: %r");
1373                 close(fd);
1374                 return;
1375         }
1376
1377         bytes = 0;
1378         do {
1379                 switch(type){
1380                 case Timage:
1381                         i = write(dfd, buf, n);
1382                         break;
1383                 default:
1384                         i = crlfwrite(dfd, buf, n);
1385                         break;
1386                 }
1387                 if(i != n){
1388                         close(fd);
1389                         close(dfd);
1390                         logit("get %s %r to data connection after %d", arg, bytes);
1391                         reply("550 Error writing to data connection: %r");
1392                         return;
1393                 }
1394                 bytes += n;
1395         } while((n = read(fd, buf, sizeof(buf))) > 0);
1396
1397         if(n < 0)
1398                 logit("get %s %r after %d", arg, bytes);
1399
1400         close(fd);
1401         close(dfd);
1402         reply("226 Transfer complete");
1403         logit("get %s OK %d", arg, bytes);
1404 }
1405 int
1406 retrievecmd(char *arg)
1407 {
1408         if(arg == 0)
1409                 return reply("501 Retrieve command requires an argument");
1410         arg = abspath(arg);
1411         if(arg == 0)
1412                 return reply("550 Permission denied");
1413
1414         return asproc(retrieve, arg, 0);
1415 }
1416
1417 /*
1418  *  get a file from the user
1419  */
1420 int
1421 lfwrite(int fd, char *p, int n)
1422 {
1423         char *ep, *np;
1424         char buf[Nbuf];
1425
1426         for(np = buf, ep = p + n; p < ep; p++){
1427                 if(*p != '\r')
1428                         *np++ = *p;
1429         }
1430         if(write(fd, buf, np - buf) == np - buf)
1431                 return n;
1432         else
1433                 return -1;
1434 }
1435 void
1436 store(char *arg, int fd)
1437 {
1438         int dfd, n, i;
1439         char buf[Nbuf];
1440
1441         reply("150 Opening data connection for %s (%s)", arg, data);
1442         dfd = dialdata();
1443         if(dfd < 0){
1444                 reply("425 Error opening data connection: %r");
1445                 close(fd);
1446                 return;
1447         }
1448
1449         while((n = read(dfd, buf, sizeof(buf))) > 0){
1450                 switch(type){
1451                 case Timage:
1452                         i = write(fd, buf, n);
1453                         break;
1454                 default:
1455                         i = lfwrite(fd, buf, n);
1456                         break;
1457                 }
1458                 if(i != n){
1459                         close(fd);
1460                         close(dfd);
1461                         reply("550 Error writing file");
1462                         return;
1463                 }
1464         }
1465         close(fd);
1466         close(dfd);
1467         logit("put %s OK", arg);
1468         reply("226 Transfer complete");
1469 }
1470 int
1471 storecmd(char *arg)
1472 {
1473         int fd, rv;
1474
1475         if(arg == 0)
1476                 return reply("501 Store command requires an argument");
1477         arg = abspath(arg);
1478         if(arg == 0)
1479                 return reply("550 Permission denied");
1480         if(isnone && strncmp(arg, "/incoming/", sizeof("/incoming/")-1))
1481                 return reply("550 Permission denied");
1482         if(offset){
1483                 fd = open(arg, OWRITE);
1484                 if(fd == -1)
1485                         return reply("550 Error opening %s: %r", arg);
1486                 if(seek(fd, offset, 0) == -1)
1487                         return reply("550 Error seeking %s to %d: %r",
1488                                 arg, offset);
1489         } else {
1490                 fd = create(arg, OWRITE, createperm);
1491                 if(fd == -1)
1492                         return reply("550 Error creating %s: %r", arg);
1493         }
1494
1495         rv = asproc(store, arg, fd);
1496         close(fd);
1497         return rv;
1498 }
1499 int
1500 appendcmd(char *arg)
1501 {
1502         int fd, rv;
1503
1504         if(arg == 0)
1505                 return reply("501 Append command requires an argument");
1506         if(isnone)
1507                 return reply("550 Permission denied");
1508         arg = abspath(arg);
1509         if(arg == 0)
1510                 return reply("550 Error creating %s: Permission denied", arg);
1511         fd = open(arg, OWRITE);
1512         if(fd == -1){
1513                 fd = create(arg, OWRITE, createperm);
1514                 if(fd == -1)
1515                         return reply("550 Error creating %s: %r", arg);
1516         }
1517         seek(fd, 0, 2);
1518
1519         rv = asproc(store, arg, fd);
1520         close(fd);
1521         return rv;
1522 }
1523 int
1524 storeucmd(char *arg)
1525 {
1526         int fd, rv;
1527         char name[Maxpath];
1528
1529         USED(arg);
1530         if(isnone)
1531                 return reply("550 Permission denied");
1532         strncpy(name, "ftpXXXXXXXXXXX", sizeof name);
1533         mktemp(name);
1534         fd = create(name, OWRITE, createperm);
1535         if(fd == -1)
1536                 return reply("550 Error creating %s: %r", name);
1537
1538         rv = asproc(store, name, fd);
1539         close(fd);
1540         return rv;
1541 }
1542
1543 int
1544 mkdircmd(char *name)
1545 {
1546         int fd;
1547
1548         if(name == 0)
1549                 return reply("501 Mkdir command requires an argument");
1550         if(isnone)
1551                 return reply("550 Permission denied");
1552         name = abspath(name);
1553         if(name == 0)
1554                 return reply("550 Permission denied");
1555         fd = create(name, OREAD, DMDIR|0775);
1556         if(fd < 0)
1557                 return reply("550 Can't create %s: %r", name);
1558         close(fd);
1559         return reply("226 %s created", name);
1560 }
1561
1562 int
1563 delcmd(char *name)
1564 {
1565         if(name == 0)
1566                 return reply("501 Rmdir/delete command requires an argument");
1567         if(isnone)
1568                 return reply("550 Permission denied");
1569         name = abspath(name);
1570         if(name == 0)
1571                 return reply("550 Permission denied");
1572         if(remove(name) < 0)
1573                 return reply("550 Can't remove %s: %r", name);
1574         else
1575                 return reply("226 %s removed", name);
1576 }
1577
1578 /*
1579  *  kill off the last transfer (if the process still exists)
1580  */
1581 int
1582 abortcmd(char *arg)
1583 {
1584         USED(arg);
1585
1586         logit("abort pid %d", pid);
1587         if(pid){
1588                 if(postnote(PNPROC, pid, "kill") == 0)
1589                         reply("426 Command aborted");
1590                 else
1591                         logit("postnote pid %d %r", pid);
1592         }
1593         return reply("226 Abort processed");
1594 }
1595
1596 int
1597 systemcmd(char *arg)
1598 {
1599         USED(arg);
1600         return reply("215 UNIX Type: L8 Version: Plan 9");
1601 }
1602
1603 int
1604 helpcmd(char *arg)
1605 {
1606         int i;
1607         char buf[80];
1608         char *p, *e;
1609
1610         USED(arg);
1611         reply("214- the following commands are implemented:");
1612         buf[0] = 0;
1613         p = buf;
1614         e = buf+sizeof buf;
1615         for(i = 0; cmdtab[i].name; i++){
1616                 if((i%8) == 0){
1617                         reply("214-%s", buf);
1618                         p = buf;
1619                 }
1620                 p = seprint(p, e, " %-5.5s", cmdtab[i].name);
1621         }
1622         if(p != buf)
1623                 reply("214-%s", buf);
1624         reply("214 ");
1625         return 0;
1626 }
1627
1628 /*
1629  *  renaming a file takes two commands
1630  */
1631 static String *filepath;
1632
1633 int
1634 rnfrcmd(char *from)
1635 {
1636         if(isnone)
1637                 return reply("550 Permission denied");
1638         if(from == 0)
1639                 return reply("501 Rename command requires an argument");
1640         from = abspath(from);
1641         if(from == 0)
1642                 return reply("550 Permission denied");
1643         if(filepath == nil)
1644                 filepath = s_copy(from);
1645         else{
1646                 s_reset(filepath);
1647                 s_append(filepath, from);
1648         }
1649         return reply("350 Rename %s to ...", s_to_c(filepath));
1650 }
1651 int
1652 rntocmd(char *to)
1653 {
1654         int r;
1655         Dir nd;
1656         char *fp, *tp;
1657
1658         if(isnone)
1659                 return reply("550 Permission denied");
1660         if(to == 0)
1661                 return reply("501 Rename command requires an argument");
1662         to = abspath(to);
1663         if(to == 0)
1664                 return reply("550 Permission denied");
1665         if(filepath == nil || *(s_to_c(filepath)) == 0)
1666                 return reply("503 Rnto must be preceeded by an rnfr");
1667
1668         tp = strrchr(to, '/');
1669         fp = strrchr(s_to_c(filepath), '/');
1670         if((tp && fp == 0) || (fp && tp == 0)
1671         || (fp && tp && (fp-s_to_c(filepath) != tp-to || memcmp(s_to_c(filepath), to, tp-to))))
1672                 return reply("550 Rename can't change directory");
1673         if(tp)
1674                 to = tp+1;
1675
1676         nulldir(&nd);
1677         nd.name = to;
1678         if(dirwstat(s_to_c(filepath), &nd) < 0)
1679                 r = reply("550 Can't rename %s to %s: %r\n", s_to_c(filepath), to);
1680         else
1681                 r = reply("250 %s now %s", s_to_c(filepath), to);
1682         s_reset(filepath);
1683
1684         return r;
1685 }
1686
1687 /*
1688  *  to dial out we need the network file system in our
1689  *  name space.
1690  */
1691 int
1692 dialdata(void)
1693 {
1694         int fd, cfd;
1695         char ldir[40];
1696         char err[ERRMAX];
1697
1698         if(mountnet() < 0)
1699                 return -1;
1700
1701         if(!passive.inuse)
1702                 fd = dial(data, "20", 0, 0);
1703         else {
1704                 fd = -1;
1705                 alarm(5*60*1000);
1706                 cfd = listen(passive.adir, ldir);
1707                 alarm(0);
1708                 if(cfd >= 0){
1709                         fd = accept(cfd, ldir);
1710                         close(cfd);
1711                 }
1712         }
1713         err[0] = 0;
1714         errstr(err, sizeof err);
1715         if(fd < 0)
1716                 logit("can't dial %s: %s", data, err);
1717         unmountnet();
1718         errstr(err, sizeof err);
1719         return fd;
1720 }
1721
1722 int
1723 postnote(int group, int pid, char *note)
1724 {
1725         char file[128];
1726         int f, r;
1727
1728         /*
1729          * Use #p because /proc may not be in the namespace.
1730          */
1731         switch(group) {
1732         case PNPROC:
1733                 sprint(file, "#p/%d/note", pid);
1734                 break;
1735         case PNGROUP:
1736                 sprint(file, "#p/%d/notepg", pid);
1737                 break;
1738         default:
1739                 return -1;
1740         }
1741
1742         f = open(file, OWRITE);
1743         if(f < 0)
1744                 return -1;
1745
1746         r = strlen(note);
1747         if(write(f, note, r) != r) {
1748                 close(f);
1749                 return -1;
1750         }
1751         close(f);
1752         return 0;
1753 }
1754
1755 /*
1756  *  to circumscribe the accessible files we have to eliminate ..'s
1757  *  and resolve all names from the root.  We also remove any /bin/rc
1758  *  special characters to avoid later problems with executed commands.
1759  */
1760 char *special = "`;| ";
1761
1762 char*
1763 abspath(char *origpath)
1764 {
1765         char *p, *sp, *path;
1766         static String *rpath;
1767
1768         if(rpath == nil)
1769                 rpath = s_new();
1770         else
1771                 s_reset(rpath);
1772
1773         if(origpath == nil)
1774                 s_append(rpath, curdir);
1775         else{
1776                 if(*origpath != '/'){
1777                         s_append(rpath, curdir);
1778                         s_append(rpath, "/");
1779                 }
1780                 s_append(rpath, origpath);
1781         }
1782         path = s_to_c(rpath);
1783
1784         for(sp = special; *sp; sp++){
1785                 p = strchr(path, *sp);
1786                 if(p)
1787                         *p = 0;
1788         }
1789
1790         cleanname(s_to_c(rpath));
1791         rpath->ptr = rpath->base+strlen(rpath->base);
1792
1793         if(!accessok(s_to_c(rpath)))
1794                 return nil;
1795
1796         return s_to_c(rpath);
1797 }
1798
1799 typedef struct Path Path;
1800 struct Path {
1801         Path    *next;
1802         String  *path;
1803         int     inuse;
1804         int     ok;
1805 };
1806
1807 enum
1808 {
1809         Maxlevel = 16,
1810         Maxperlevel= 8,
1811 };
1812
1813 Path *pathlevel[Maxlevel];
1814
1815 Path*
1816 unlinkpath(char *path, int level)
1817 {
1818         String *s;
1819         Path **l, *p;
1820         int n;
1821
1822         n = 0;
1823         for(l = &pathlevel[level]; *l; l = &(*l)->next){
1824                 p = *l;
1825                 /* hit */
1826                 if(strcmp(s_to_c(p->path), path) == 0){
1827                         *l = p->next;
1828                         p->next = nil;
1829                         return p;
1830                 }
1831                 /* reuse */
1832                 if(++n >= Maxperlevel){
1833                         *l = p->next;
1834                         s = p->path;
1835                         s_reset(p->path);
1836                         memset(p, 0, sizeof *p);
1837                         p->path = s_append(s, path);
1838                         return p;
1839                 }
1840         }
1841
1842         /* allocate */
1843         p = mallocz(sizeof *p, 1);
1844         p->path = s_copy(path);
1845         return p;
1846 }
1847
1848 void
1849 linkpath(Path *p, int level)
1850 {
1851         p->next = pathlevel[level];
1852         pathlevel[level] = p;
1853         p->inuse = 1;
1854 }
1855
1856 void
1857 addpath(Path *p, int level, int ok)
1858 {
1859         p->ok = ok;
1860         p->next = pathlevel[level];
1861         pathlevel[level] = p;
1862 }
1863
1864 int
1865 _accessok(String *s, int level)
1866 {
1867         Path *p;
1868         char *cp;
1869         int lvl, offset;
1870         static char httplogin[] = "/.httplogin";
1871
1872         if(level < 0)
1873                 return 1;
1874         lvl = level;
1875         if(lvl >= Maxlevel)
1876                 lvl = Maxlevel - 1;
1877
1878         p = unlinkpath(s_to_c(s), lvl);
1879         if(p->inuse){
1880                 /* move to front */
1881                 linkpath(p, lvl);
1882                 return p->ok;
1883         }
1884         cp = strrchr(s_to_c(s), '/');
1885         if(cp == nil)
1886                 offset = 0;
1887         else
1888                 offset = cp - s_to_c(s);
1889         s_append(s, httplogin);
1890         if(access(s_to_c(s), AEXIST) == 0){
1891                 addpath(p, lvl, 0);
1892                 return 0;
1893         }
1894
1895         /*
1896          * There's no way to shorten a String without
1897          * knowing the implementation.
1898          */
1899         s->ptr = s->base+offset;
1900         s_terminate(s);
1901         addpath(p, lvl, _accessok(s, level-1));
1902
1903         return p->ok;
1904 }
1905
1906 /*
1907  * check for a subdirectory containing .httplogin
1908  * at each level of the path.
1909  */
1910 int
1911 accessok(char *path)
1912 {
1913         int level, r;
1914         char *p;
1915         String *npath;
1916
1917         npath = s_copy(path);
1918         p = s_to_c(npath)+1;
1919         for(level = 1; level < Maxlevel; level++){
1920                 p = strchr(p, '/');
1921                 if(p == nil)
1922                         break;
1923                 p++;
1924         }
1925
1926         r = _accessok(npath, level-1);
1927         s_free(npath);
1928
1929         return r;
1930 }