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