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