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 } 1913