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