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