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
logit(char * fmt,...)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
usage(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
main(int argc,char ** argv)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
reply(char * fmt,...)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
sodoff(void)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
asproc(void (* f)(char *,int),char * arg,int arg2)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
transfer(char * cmd,char * a1,char * a2,char * a3,int image)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
nopcmd(char * arg)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
loginuser(char * user,char * nsfile,int gotoslash)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
slowdown(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
usercmd(char * name)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
passcmd(char * response)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
pwdcmd(char * arg)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
cwdcmd(char * dir)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
cdupcmd(char * dp)682 cdupcmd(char *dp)
683 {
684 USED(dp);
685 return cwdcmd("..");
686 }
687
688 int
quitcmd(char * arg)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
typecmd(char * arg)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
modecmd(char * arg)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
structcmd(char * arg)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
portcmd(char * arg)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
mountnet(void)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
unmountnet(void)803 unmountnet(void)
804 {
805 unmount(0, "/net");
806 unmount("#/", "/");
807 }
808
809 int
pasvcmd(char * arg)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*
mode2asc(int m)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
listfile(Biobufhdr * b,char * name,int lflag,char * dname)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
dircomp(void * va,void * vb)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
listdir(char * name,Biobufhdr * b,int lflag,int * printname,Globlist * gl)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
list(char * arg,int lflag)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
namelistcmd(char * arg)1116 namelistcmd(char *arg)
1117 {
1118 return asproc(list, arg, 0);
1119 }
1120 int
listcmd(char * arg)1121 listcmd(char *arg)
1122 {
1123 return asproc(list, arg, 1);
1124 }
1125
1126 /*
1127 * fuse compatability
1128 */
1129 int
oksiteuser(void)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
sitecmd(char * arg)1149 sitecmd(char *arg)
1150 {
1151 char *f[4];
1152 int nf, r;
1153 Dir *d;
1154
1155 if(arg == 0)
1156 return reply("501 bad site command");
1157 nf = tokenize(arg, f, nelem(f));
1158 if(nf != 3 || cistrcmp(f[0], "chmod") != 0)
1159 return reply("501 bad site command");
1160 if(!oksiteuser())
1161 return reply("550 Permission denied");
1162 d = dirstat(f[2]);
1163 if(d == nil)
1164 return reply("501 site chmod: file does not exist");
1165 d->mode &= ~0777;
1166 d->mode |= strtoul(f[1], 0, 8) & 0777;
1167 r = dirwstat(f[2], d);
1168 free(d);
1169 if(r < 0)
1170 return reply("550 Permission denied %r");
1171 return reply("200 very well, then");
1172 }
1173
1174 /*
1175 * return the size of the file
1176 */
1177 int
sizecmd(char * arg)1178 sizecmd(char *arg)
1179 {
1180 Dir *d;
1181 int rv;
1182
1183 if(arg == 0)
1184 return reply("501 Size command requires pathname");
1185 arg = abspath(arg);
1186 d = dirstat(arg);
1187 if(d == nil)
1188 return reply("501 %r accessing %s", arg);
1189 rv = reply("213 %lld", d->length);
1190 free(d);
1191 return rv;
1192 }
1193
1194 /*
1195 * return the modify time of the file
1196 */
1197 int
mdtmcmd(char * arg)1198 mdtmcmd(char *arg)
1199 {
1200 Dir *d;
1201 Tm *t;
1202 int rv;
1203
1204 if(arg == 0)
1205 return reply("501 Mdtm command requires pathname");
1206 if(arg == 0)
1207 return reply("550 Permission denied");
1208 d = dirstat(arg);
1209 if(d == nil)
1210 return reply("501 %r accessing %s", arg);
1211 t = gmtime(d->mtime);
1212 rv = reply("213 %4.4d%2.2d%2.2d%2.2d%2.2d%2.2d",
1213 t->year+1900, t->mon+1, t->mday,
1214 t->hour, t->min, t->sec);
1215 free(d);
1216 return rv;
1217 }
1218
1219 /*
1220 * set an offset to start reading a file from
1221 * only lasts for one command
1222 */
1223 int
restartcmd(char * arg)1224 restartcmd(char *arg)
1225 {
1226 if(arg == 0)
1227 return reply("501 Restart command requires offset");
1228 offset = atoll(arg);
1229 if(offset < 0){
1230 offset = 0;
1231 return reply("501 Bad offset");
1232 }
1233
1234 return reply("350 Restarting at %lld. Send STORE or RETRIEVE", offset);
1235 }
1236
1237 /*
1238 * send a file to the user
1239 */
1240 int
crlfwrite(int fd,char * p,int n)1241 crlfwrite(int fd, char *p, int n)
1242 {
1243 char *ep, *np;
1244 char buf[2*Nbuf];
1245
1246 for(np = buf, ep = p + n; p < ep; p++){
1247 if(*p == '\n')
1248 *np++ = '\r';
1249 *np++ = *p;
1250 }
1251 if(write(fd, buf, np - buf) == np - buf)
1252 return n;
1253 else
1254 return -1;
1255 }
1256 void
retrievedir(char * arg)1257 retrievedir(char *arg)
1258 {
1259 int n;
1260 char *p;
1261 String *file;
1262
1263 if(type != Timage){
1264 reply("550 This file requires type binary/image");
1265 return;
1266 }
1267
1268 file = s_copy(arg);
1269 p = strrchr(s_to_c(file), '/');
1270 if(p != s_to_c(file)){
1271 *p++ = 0;
1272 chdir(s_to_c(file));
1273 } else {
1274 chdir("/");
1275 p = s_to_c(file)+1;
1276 }
1277
1278 n = transfer("/bin/tar", "c", p, 0, 1);
1279 if(n < 0)
1280 logit("get %s failed", arg);
1281 else
1282 logit("get %s OK %d", arg, n);
1283 s_free(file);
1284 }
1285 void
retrieve(char * arg,int arg2)1286 retrieve(char *arg, int arg2)
1287 {
1288 int dfd, fd, n, i, bytes;
1289 Dir *d;
1290 char buf[Nbuf];
1291 char *p, *ep;
1292
1293 USED(arg2);
1294
1295 p = strchr(arg, '\r');
1296 if(p){
1297 logit("cr in file name", arg);
1298 *p = 0;
1299 }
1300
1301 fd = open(arg, OREAD);
1302 if(fd == -1){
1303 n = strlen(arg);
1304 if(n > 4 && strcmp(arg+n-4, ".tar") == 0){
1305 *(arg+n-4) = 0;
1306 d = dirstat(arg);
1307 if(d != nil){
1308 if(d->qid.type & QTDIR){
1309 retrievedir(arg);
1310 free(d);
1311 return;
1312 }
1313 free(d);
1314 }
1315 }
1316 logit("get %s failed", arg);
1317 reply("550 Error opening %s: %r", arg);
1318 return;
1319 }
1320 if(offset != 0)
1321 if(seek(fd, offset, 0) < 0){
1322 reply("550 %s: seek to %lld failed", arg, offset);
1323 close(fd);
1324 return;
1325 }
1326 d = dirfstat(fd);
1327 if(d != nil){
1328 if(d->qid.type & QTDIR){
1329 reply("550 %s: not a plain file.", arg);
1330 close(fd);
1331 free(d);
1332 return;
1333 }
1334 free(d);
1335 }
1336
1337 n = read(fd, buf, sizeof(buf));
1338 if(n < 0){
1339 logit("get %s failed", arg, mailaddr, nci->rsys);
1340 reply("550 Error reading %s: %r", arg);
1341 close(fd);
1342 return;
1343 }
1344
1345 if(type != Timage)
1346 for(p = buf, ep = &buf[n]; p < ep; p++)
1347 if(*p & 0x80){
1348 close(fd);
1349 reply("550 This file requires type binary/image");
1350 return;
1351 }
1352
1353 reply("150 Opening data connection for %s (%s)", arg, data);
1354 dfd = dialdata();
1355 if(dfd < 0){
1356 reply("425 Error opening data connection:%r");
1357 close(fd);
1358 return;
1359 }
1360
1361 bytes = 0;
1362 do {
1363 switch(type){
1364 case Timage:
1365 i = write(dfd, buf, n);
1366 break;
1367 default:
1368 i = crlfwrite(dfd, buf, n);
1369 break;
1370 }
1371 if(i != n){
1372 close(fd);
1373 close(dfd);
1374 logit("get %s %r to data connection after %d", arg, bytes);
1375 reply("550 Error writing to data connection: %r");
1376 return;
1377 }
1378 bytes += n;
1379 } while((n = read(fd, buf, sizeof(buf))) > 0);
1380
1381 if(n < 0)
1382 logit("get %s %r after %d", arg, bytes);
1383
1384 close(fd);
1385 close(dfd);
1386 reply("226 Transfer complete");
1387 logit("get %s OK %d", arg, bytes);
1388 }
1389 int
retrievecmd(char * arg)1390 retrievecmd(char *arg)
1391 {
1392 if(arg == 0)
1393 return reply("501 Retrieve command requires an argument");
1394 arg = abspath(arg);
1395 if(arg == 0)
1396 return reply("550 Permission denied");
1397
1398 return asproc(retrieve, arg, 0);
1399 }
1400
1401 /*
1402 * get a file from the user
1403 */
1404 int
lfwrite(int fd,char * p,int n)1405 lfwrite(int fd, char *p, int n)
1406 {
1407 char *ep, *np;
1408 char buf[Nbuf];
1409
1410 for(np = buf, ep = p + n; p < ep; p++){
1411 if(*p != '\r')
1412 *np++ = *p;
1413 }
1414 if(write(fd, buf, np - buf) == np - buf)
1415 return n;
1416 else
1417 return -1;
1418 }
1419 void
store(char * arg,int fd)1420 store(char *arg, int fd)
1421 {
1422 int dfd, n, i;
1423 char buf[Nbuf];
1424
1425 reply("150 Opening data connection for %s (%s)", arg, data);
1426 dfd = dialdata();
1427 if(dfd < 0){
1428 reply("425 Error opening data connection:%r");
1429 close(fd);
1430 return;
1431 }
1432
1433 while((n = read(dfd, buf, sizeof(buf))) > 0){
1434 switch(type){
1435 case Timage:
1436 i = write(fd, buf, n);
1437 break;
1438 default:
1439 i = lfwrite(fd, buf, n);
1440 break;
1441 }
1442 if(i != n){
1443 close(fd);
1444 close(dfd);
1445 reply("550 Error writing file");
1446 return;
1447 }
1448 }
1449 close(fd);
1450 close(dfd);
1451 logit("put %s OK", arg);
1452 reply("226 Transfer complete");
1453 }
1454 int
storecmd(char * arg)1455 storecmd(char *arg)
1456 {
1457 int fd, rv;
1458
1459 if(arg == 0)
1460 return reply("501 Store command requires an argument");
1461 arg = abspath(arg);
1462 if(arg == 0)
1463 return reply("550 Permission denied");
1464 if(isnone && strncmp(arg, "/incoming/", sizeof("/incoming/")-1))
1465 return reply("550 Permission denied");
1466 if(offset){
1467 fd = open(arg, OWRITE);
1468 if(fd == -1)
1469 return reply("550 Error opening %s: %r", arg);
1470 if(seek(fd, offset, 0) == -1)
1471 return reply("550 Error seeking %s to %d: %r",
1472 arg, offset);
1473 } else {
1474 fd = create(arg, OWRITE, createperm);
1475 if(fd == -1)
1476 return reply("550 Error creating %s: %r", arg);
1477 }
1478
1479 rv = asproc(store, arg, fd);
1480 close(fd);
1481 return rv;
1482 }
1483 int
appendcmd(char * arg)1484 appendcmd(char *arg)
1485 {
1486 int fd, rv;
1487
1488 if(arg == 0)
1489 return reply("501 Append command requires an argument");
1490 if(isnone)
1491 return reply("550 Permission denied");
1492 arg = abspath(arg);
1493 if(arg == 0)
1494 return reply("550 Error creating %s: Permission denied", arg);
1495 fd = open(arg, OWRITE);
1496 if(fd == -1){
1497 fd = create(arg, OWRITE, createperm);
1498 if(fd == -1)
1499 return reply("550 Error creating %s: %r", arg);
1500 }
1501 seek(fd, 0, 2);
1502
1503 rv = asproc(store, arg, fd);
1504 close(fd);
1505 return rv;
1506 }
1507 int
storeucmd(char * arg)1508 storeucmd(char *arg)
1509 {
1510 int fd, rv;
1511 char name[Maxpath];
1512
1513 USED(arg);
1514 if(isnone)
1515 return reply("550 Permission denied");
1516 strncpy(name, "ftpXXXXXXXXXXX", sizeof name);
1517 mktemp(name);
1518 fd = create(name, OWRITE, createperm);
1519 if(fd == -1)
1520 return reply("550 Error creating %s: %r", name);
1521
1522 rv = asproc(store, name, fd);
1523 close(fd);
1524 return rv;
1525 }
1526
1527 int
mkdircmd(char * name)1528 mkdircmd(char *name)
1529 {
1530 int fd;
1531
1532 if(name == 0)
1533 return reply("501 Mkdir command requires an argument");
1534 if(isnone)
1535 return reply("550 Permission denied");
1536 name = abspath(name);
1537 if(name == 0)
1538 return reply("550 Permission denied");
1539 fd = create(name, OREAD, DMDIR|0775);
1540 if(fd < 0)
1541 return reply("550 Can't create %s: %r", name);
1542 close(fd);
1543 return reply("226 %s created", name);
1544 }
1545
1546 int
delcmd(char * name)1547 delcmd(char *name)
1548 {
1549 if(name == 0)
1550 return reply("501 Rmdir/delete command requires an argument");
1551 if(isnone)
1552 return reply("550 Permission denied");
1553 name = abspath(name);
1554 if(name == 0)
1555 return reply("550 Permission denied");
1556 if(remove(name) < 0)
1557 return reply("550 Can't remove %s: %r", name);
1558 else
1559 return reply("226 %s removed", name);
1560 }
1561
1562 /*
1563 * kill off the last transfer (if the process still exists)
1564 */
1565 int
abortcmd(char * arg)1566 abortcmd(char *arg)
1567 {
1568 USED(arg);
1569
1570 logit("abort pid %d", pid);
1571 if(pid){
1572 if(postnote(PNPROC, pid, "kill") == 0)
1573 reply("426 Command aborted");
1574 else
1575 logit("postnote pid %d %r", pid);
1576 }
1577 return reply("226 Abort processed");
1578 }
1579
1580 int
systemcmd(char * arg)1581 systemcmd(char *arg)
1582 {
1583 USED(arg);
1584 return reply("215 UNIX Type: L8 Version: Plan 9");
1585 }
1586
1587 int
helpcmd(char * arg)1588 helpcmd(char *arg)
1589 {
1590 int i;
1591 char buf[80];
1592 char *p, *e;
1593
1594 USED(arg);
1595 reply("214- the following commands are implemented:");
1596 p = buf;
1597 e = buf+sizeof buf;
1598 for(i = 0; cmdtab[i].name; i++){
1599 if((i%8) == 0){
1600 reply("214-%s", buf);
1601 p = buf;
1602 }
1603 p = seprint(p, e, " %-5.5s", cmdtab[i].name);
1604 }
1605 if(p != buf)
1606 reply("214-%s", buf);
1607 reply("214 ");
1608 return 0;
1609 }
1610
1611 /*
1612 * renaming a file takes two commands
1613 */
1614 static String *filepath;
1615
1616 int
rnfrcmd(char * from)1617 rnfrcmd(char *from)
1618 {
1619 if(isnone)
1620 return reply("550 Permission denied");
1621 if(from == 0)
1622 return reply("501 Rename command requires an argument");
1623 from = abspath(from);
1624 if(from == 0)
1625 return reply("550 Permission denied");
1626 if(filepath == nil)
1627 filepath = s_copy(from);
1628 else{
1629 s_reset(filepath);
1630 s_append(filepath, from);
1631 }
1632 return reply("350 Rename %s to ...", s_to_c(filepath));
1633 }
1634 int
rntocmd(char * to)1635 rntocmd(char *to)
1636 {
1637 int r;
1638 Dir nd;
1639 char *fp, *tp;
1640
1641 if(isnone)
1642 return reply("550 Permission denied");
1643 if(to == 0)
1644 return reply("501 Rename command requires an argument");
1645 to = abspath(to);
1646 if(to == 0)
1647 return reply("550 Permission denied");
1648 if(filepath == nil || *(s_to_c(filepath)) == 0)
1649 return reply("503 Rnto must be preceeded by an rnfr");
1650
1651 tp = strrchr(to, '/');
1652 fp = strrchr(s_to_c(filepath), '/');
1653 if((tp && fp == 0) || (fp && tp == 0)
1654 || (fp && tp && (fp-s_to_c(filepath) != tp-to || memcmp(s_to_c(filepath), to, tp-to))))
1655 return reply("550 Rename can't change directory");
1656 if(tp)
1657 to = tp+1;
1658
1659 nulldir(&nd);
1660 nd.name = to;
1661 if(dirwstat(s_to_c(filepath), &nd) < 0)
1662 r = reply("550 Can't rename %s to %s: %r\n", s_to_c(filepath), to);
1663 else
1664 r = reply("250 %s now %s", s_to_c(filepath), to);
1665 s_reset(filepath);
1666
1667 return r;
1668 }
1669
1670 /*
1671 * to dial out we need the network file system in our
1672 * name space.
1673 */
1674 int
dialdata(void)1675 dialdata(void)
1676 {
1677 int fd, cfd;
1678 char ldir[40];
1679 char err[Maxerr];
1680
1681 if(mountnet() < 0)
1682 return -1;
1683
1684 if(!passive.inuse){
1685 fd = dial(data, "20", 0, 0);
1686 errstr(err, sizeof err);
1687 } else {
1688 alarm(5*60*1000);
1689 cfd = listen(passive.adir, ldir);
1690 alarm(0);
1691 errstr(err, sizeof err);
1692 if(cfd < 0)
1693 return -1;
1694 fd = accept(cfd, ldir);
1695 errstr(err, sizeof err);
1696 close(cfd);
1697 }
1698 if(fd < 0)
1699 logit("can't dial %s: %s", data, err);
1700
1701 unmountnet();
1702 werrstr(err, sizeof err);
1703 return fd;
1704 }
1705
1706 int
postnote(int group,int pid,char * note)1707 postnote(int group, int pid, char *note)
1708 {
1709 char file[128];
1710 int f, r;
1711
1712 /*
1713 * Use #p because /proc may not be in the namespace.
1714 */
1715 switch(group) {
1716 case PNPROC:
1717 sprint(file, "#p/%d/note", pid);
1718 break;
1719 case PNGROUP:
1720 sprint(file, "#p/%d/notepg", pid);
1721 break;
1722 default:
1723 return -1;
1724 }
1725
1726 f = open(file, OWRITE);
1727 if(f < 0)
1728 return -1;
1729
1730 r = strlen(note);
1731 if(write(f, note, r) != r) {
1732 close(f);
1733 return -1;
1734 }
1735 close(f);
1736 return 0;
1737 }
1738
1739 /*
1740 * to circumscribe the accessible files we have to eliminate ..'s
1741 * and resolve all names from the root. We also remove any /bin/rc
1742 * special characters to avoid later problems with executed commands.
1743 */
1744 char *special = "`;| ";
1745
1746 char*
abspath(char * origpath)1747 abspath(char *origpath)
1748 {
1749 char *p, *sp, *path;
1750 static String *rpath;
1751
1752 if(rpath == nil)
1753 rpath = s_new();
1754 else
1755 s_reset(rpath);
1756
1757 if(origpath == nil)
1758 s_append(rpath, curdir);
1759 else{
1760 if(*origpath != '/'){
1761 s_append(rpath, curdir);
1762 s_append(rpath, "/");
1763 }
1764 s_append(rpath, origpath);
1765 }
1766 path = s_to_c(rpath);
1767
1768 for(sp = special; *sp; sp++){
1769 p = strchr(path, *sp);
1770 if(p)
1771 *p = 0;
1772 }
1773
1774 cleanname(s_to_c(rpath));
1775 rpath->ptr = rpath->base+strlen(rpath->base);
1776
1777 if(!accessok(s_to_c(rpath)))
1778 return nil;
1779
1780 return s_to_c(rpath);
1781 }
1782
1783 typedef struct Path Path;
1784 struct Path {
1785 Path *next;
1786 String *path;
1787 int inuse;
1788 int ok;
1789 };
1790
1791 enum
1792 {
1793 Maxlevel = 16,
1794 Maxperlevel= 8,
1795 };
1796
1797 Path *pathlevel[Maxlevel];
1798
1799 Path*
unlinkpath(char * path,int level)1800 unlinkpath(char *path, int level)
1801 {
1802 String *s;
1803 Path **l, *p;
1804 int n;
1805
1806 n = 0;
1807 for(l = &pathlevel[level]; *l; l = &(*l)->next){
1808 p = *l;
1809 /* hit */
1810 if(strcmp(s_to_c(p->path), path) == 0){
1811 *l = p->next;
1812 p->next = nil;
1813 return p;
1814 }
1815 /* reuse */
1816 if(++n >= Maxperlevel){
1817 *l = p->next;
1818 s = p->path;
1819 s_reset(p->path);
1820 memset(p, 0, sizeof *p);
1821 p->path = s_append(s, path);
1822 return p;
1823 }
1824 }
1825
1826 /* allocate */
1827 p = mallocz(sizeof *p, 1);
1828 p->path = s_copy(path);
1829 return p;
1830 }
1831
1832 void
linkpath(Path * p,int level)1833 linkpath(Path *p, int level)
1834 {
1835 p->next = pathlevel[level];
1836 pathlevel[level] = p;
1837 p->inuse = 1;
1838 }
1839
1840 void
addpath(Path * p,int level,int ok)1841 addpath(Path *p, int level, int ok)
1842 {
1843 p->ok = ok;
1844 p->next = pathlevel[level];
1845 pathlevel[level] = p;
1846 }
1847
1848 int
_accessok(String * s,int level)1849 _accessok(String *s, int level)
1850 {
1851 Path *p;
1852 char *cp;
1853 int lvl, offset;
1854 static char httplogin[] = "/.httplogin";
1855
1856 if(level < 0)
1857 return 1;
1858 lvl = level;
1859 if(lvl >= Maxlevel)
1860 lvl = Maxlevel - 1;
1861
1862 p = unlinkpath(s_to_c(s), lvl);
1863 if(p->inuse){
1864 /* move to front */
1865 linkpath(p, lvl);
1866 return p->ok;
1867 }
1868 cp = strrchr(s_to_c(s), '/');
1869 if(cp == nil)
1870 offset = 0;
1871 else
1872 offset = cp - s_to_c(s);
1873 s_append(s, httplogin);
1874 if(access(s_to_c(s), AEXIST) == 0){
1875 addpath(p, lvl, 0);
1876 return 0;
1877 }
1878
1879 /*
1880 * There's no way to shorten a String without
1881 * knowing the implementation.
1882 */
1883 s->ptr = s->base+offset;
1884 s_terminate(s);
1885 addpath(p, lvl, _accessok(s, level-1));
1886
1887 return p->ok;
1888 }
1889
1890 /*
1891 * check for a subdirectory containing .httplogin
1892 * at each level of the path.
1893 */
1894 int
accessok(char * path)1895 accessok(char *path)
1896 {
1897 int level, r;
1898 char *p;
1899 String *npath;
1900
1901 npath = s_copy(path);
1902 p = s_to_c(npath)+1;
1903 for(level = 1; level < Maxlevel; level++){
1904 p = strchr(p, '/');
1905 if(p == nil)
1906 break;
1907 p++;
1908 }
1909
1910 r = _accessok(npath, level-1);
1911 s_free(npath);
1912
1913 return r;
1914 }
1915