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