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