1 #include <u.h> 2 #include <libc.h> 3 #include <fcall.h> 4 #include <thread.h> 5 #include <libsec.h> 6 #include <9p.h> 7 #include "cifs.h" 8 9 #define max(a,b) (((a) > (b))? (a): (b)) 10 #define min(a,b) (((a) < (b))? (a): (b)) 11 12 typedef struct Aux Aux; 13 struct Aux { 14 Aux *next; 15 Aux *prev; 16 char *path; /* full path fo file */ 17 Share *sp; /* this share's info */ 18 long expire; /* expiration time of cache */ 19 long off; /* file pos of start of cache */ 20 long end; /* file pos of end of cache */ 21 char *cache; 22 int fh; /* file handle */ 23 int sh; /* search handle */ 24 long srch; /* find first's internal state */ 25 }; 26 27 extern int chatty9p; 28 29 int Checkcase = 1; /* enforce case significance on filenames */ 30 int Dfstout = 100; /* timeout (in ms) for ping of dfs servers (assume they are local) */ 31 int Billtrog = 1; /* enable file owner/group resolution */ 32 int Attachpid; /* pid of proc that attaches (ugh !) */ 33 char *Debug = nil; /* messages */ 34 Qid Root; /* root of remote system */ 35 Share Ipc; /* Share info of IPC$ share */ 36 Session *Sess; /* current session */ 37 int Active = IDLE_TIME; /* secs until next keepalive is sent */ 38 static int Keeppid; /* process ID of keepalive thread */ 39 Share Shares[MAX_SHARES]; /* table of connected shares */ 40 int Nshares = 0; /* number of Shares connected */ 41 Aux *Auxroot = nil; /* linked list of Aux structs */ 42 char *Host = nil; /* host we are connected to */ 43 44 static char *Ipcname = "IPC$"; 45 46 #define ptype(x) (((x) & 0xf)) 47 #define pindex(x) (((x) & 0xff0) >> 4) 48 49 void 50 setup(void) 51 { 52 int fd; 53 char buf[32]; 54 55 /* 56 * This is revolting but I cannot see any other way to get 57 * the pid of the server. We need this as Windows doesn't 58 * drop the TCP connection when it closes a connection. 59 * Thus we keepalive() to detect when/if we are thrown off. 60 */ 61 Attachpid = getpid(); 62 63 snprint(buf, sizeof buf, "#p/%d/args", getpid()); 64 if((fd = open(buf, OWRITE)) >= 0){ 65 fprint(fd, "%s network", Host); 66 close(fd); 67 } 68 } 69 70 int 71 filetableinfo(Fmt *f) 72 { 73 Aux *ap; 74 char *type; 75 76 if((ap = Auxroot) != nil) 77 do{ 78 type = "walked"; 79 if(ap->sh != -1) 80 type = "opendir"; 81 if(ap->fh != -1) 82 type = "openfile"; 83 fmtprint(f, "%-9s %s\n", type, ap->path); 84 ap = ap->next; 85 }while(ap != Auxroot); 86 return 0; 87 } 88 89 Qid 90 mkqid(char *s, int is_dir, long vers, int subtype, long path) 91 { 92 Qid q; 93 union { /* align digest suitably */ 94 uchar digest[SHA1dlen]; 95 uvlong uvl; 96 } u; 97 98 sha1((uchar *)s, strlen(s), u.digest, nil); 99 q.type = (is_dir)? QTDIR: 0; 100 q.vers = vers; 101 if(subtype){ 102 q.path = *((uvlong *)u.digest) & ~0xfffL; 103 q.path |= ((path & 0xff) << 4); 104 q.path |= (subtype & 0xf); 105 } 106 else 107 q.path = *((uvlong *)u.digest) & ~0xfL; 108 return q; 109 } 110 111 /* 112 * used only for root dir and shares 113 */ 114 static void 115 V2D(Dir *d, Qid qid, char *name) 116 { 117 memset(d, 0, sizeof(Dir)); 118 d->type = 'C'; 119 d->dev = 1; 120 d->name = estrdup9p(name); 121 d->uid = estrdup9p("bill"); 122 d->muid = estrdup9p("boyd"); 123 d->gid = estrdup9p("trog"); 124 d->mode = 0755 | DMDIR; 125 d->atime = time(nil); 126 d->mtime = d->atime; 127 d->length = 0; 128 d->qid = qid; 129 } 130 131 static void 132 I2D(Dir *d, Share *sp, char *path, FInfo *fi) 133 { 134 char *name; 135 136 if((name = strrchr(fi->name, '\\')) != nil) 137 name++; 138 else 139 name = fi->name; 140 d->name = estrdup9p(name); 141 d->type = 'C'; 142 d->dev = sp->tid; 143 d->uid = estrdup9p("bill"); 144 d->gid = estrdup9p("trog"); 145 d->muid = estrdup9p("boyd"); 146 d->atime = fi->accessed; 147 d->mtime = fi->written; 148 149 if(fi->attribs & ATTR_READONLY) 150 d->mode = 0444; 151 else 152 d->mode = 0666; 153 154 d->length = fi->size; 155 d->qid = mkqid(path, fi->attribs & ATTR_DIRECTORY, fi->changed, 0, 0); 156 157 if(fi->attribs & ATTR_DIRECTORY){ 158 d->length = 0; 159 d->mode |= DMDIR|0111; 160 } 161 } 162 163 static void 164 responderrstr(Req *r) 165 { 166 char e[ERRMAX]; 167 168 *e = 0; 169 rerrstr(e, sizeof e); 170 respond(r, e); 171 } 172 173 static char * 174 newpath(char *path, char *name) 175 { 176 char *p, *q; 177 178 assert((p = strrchr(path, '/')) != nil); 179 180 if(strcmp(name, "..") == 0){ 181 if(p == path) 182 return estrdup9p("/"); 183 q = emalloc9p((p-path)+1); 184 strecpy(q, q+(p-path)+1, path); 185 return q; 186 } 187 if(strcmp(path, "/") == 0) 188 return smprint("/%s", name); 189 return smprint("%s/%s", path, name); 190 } 191 192 static int 193 dirgen(int slot, Dir *d, void *aux) 194 { 195 long off; 196 FInfo *fi; 197 int rc, got; 198 Aux *a = aux; 199 char *npath; 200 int numinf = numinfo(); 201 int slots = min(Sess->mtu, MTU) / sizeof(FInfo); 202 203 if(strcmp(a->path, "/") == 0){ 204 if(slot < numinf){ 205 dirgeninfo(slot, d); 206 return 0; 207 } else 208 slot -= numinf; 209 210 if(slot >= Nshares) 211 return -1; 212 V2D(d, mkqid(Shares[slot].name, 1, 1, Pshare, slot), 213 Shares[slot].name); 214 return 0; 215 } 216 217 off = slot * sizeof(FInfo); 218 if(off >= a->off && off < a->end && time(nil) < a->expire) 219 goto from_cache; 220 221 if(off == 0){ 222 fi = (FInfo *)a->cache; 223 npath = smprint("%s/*", mapfile(a->path)); 224 a->sh = T2findfirst(Sess, a->sp, slots, npath, &got, &a->srch, 225 (FInfo *)a->cache); 226 free(npath); 227 if(a->sh == -1) 228 return -1; 229 230 a->off = 0; 231 a->end = got * sizeof(FInfo); 232 233 if(got >= 2 && strcmp(fi[0].name, ".") == 0 && 234 strcmp(fi[1].name, "..") == 0){ 235 a->end = (got - 2) * sizeof(FInfo); 236 memmove(a->cache, a->cache + sizeof(FInfo)*2, 237 a->end - a->off); 238 } 239 } 240 241 while(off >= a->end && a->sh != -1){ 242 fi = (FInfo *)(a->cache + (a->end - a->off) - sizeof(FInfo)); 243 a->off = a->end; 244 npath = smprint("%s/%s", mapfile(a->path), fi->name); 245 rc = T2findnext(Sess, a->sp, slots, npath, 246 &got, &a->srch, (FInfo *)a->cache, a->sh); 247 free(npath); 248 if(rc == -1 || got == 0) 249 break; 250 a->end = a->off + got * sizeof(FInfo); 251 } 252 a->expire = time(nil) + CACHETIME; 253 254 if(got < slots){ 255 if(a->sh != -1) 256 CIFSfindclose2(Sess, a->sp, a->sh); 257 a->sh = -1; 258 } 259 260 if(off >= a->end) 261 return -1; 262 263 from_cache: 264 fi = (FInfo *)(a->cache + (off - a->off)); 265 npath = smprint("%s/%s", mapfile(a->path), fi->name); 266 I2D(d, a->sp, npath, fi); 267 if(Billtrog == 0) 268 upd_names(Sess, a->sp, npath, d); 269 free(npath); 270 return 0; 271 } 272 273 static void 274 fsattach(Req *r) 275 { 276 Aux *a; 277 static int first = 1; 278 char *spec = r->ifcall.aname; 279 280 if(first) 281 setup(); 282 283 if(spec && *spec){ 284 respond(r, "invalid attach specifier"); 285 return; 286 } 287 288 r->ofcall.qid = mkqid("/", 1, 1, Proot, 0); 289 r->fid->qid = r->ofcall.qid; 290 291 a = r->fid->aux = emalloc9p(sizeof(Aux)); 292 memset(a, 0, sizeof(Aux)); 293 a->path = estrdup9p("/"); 294 a->sp = nil; 295 a->fh = -1; 296 a->sh = -1; 297 298 if(Auxroot){ 299 a->prev = Auxroot; 300 a->next = Auxroot->next; 301 Auxroot->next->prev = a; 302 Auxroot->next = a; 303 } else { 304 Auxroot = a; 305 a->next = a; 306 a->prev = a; 307 } 308 respond(r, nil); 309 } 310 311 static char* 312 fsclone(Fid *ofid, Fid *fid) 313 { 314 Aux *oa = ofid->aux; 315 Aux *a = emalloc9p(sizeof(Aux)); 316 317 fid->aux = a; 318 319 memset(a, 0, sizeof(Aux)); 320 a->sh = -1; 321 a->fh = -1; 322 a->sp = oa->sp; 323 a->path = estrdup9p(oa->path); 324 325 if(Auxroot){ 326 a->prev = Auxroot; 327 a->next = Auxroot->next; 328 Auxroot->next->prev = a; 329 Auxroot->next = a; 330 } else { 331 Auxroot = a; 332 a->next = a; 333 a->prev = a; 334 } 335 return nil; 336 } 337 338 /* 339 * for some weird reason T2queryall() returns share names 340 * in lower case so we have to do an extra test against 341 * our share table to validate filename case. 342 * 343 * on top of this here (snell & Wilcox) most of our 344 * redirections point to a share of the same name, 345 * but some do not, thus the tail of the filename 346 * returned by T2queryall() is not the same as 347 * the name we wanted. 348 * 349 * We work around this by not validating the names 350 * or files which resolve to share names as they must 351 * be correct, having been enforced in the dfs layer. 352 */ 353 static int 354 validfile(char *found, char *want, char *winpath, Share *sp) 355 { 356 char *share; 357 358 if(strcmp(want, "..") == 0) 359 return 1; 360 if(strcmp(winpath, "/") == 0){ 361 share = trimshare(sp->name); 362 if(cistrcmp(want, share) == 0) 363 return strcmp(want, share) == 0; 364 /* 365 * OK, a DFS redirection points us from a directory XXX 366 * to a share named YYY. There is no case checking we can 367 * do so we allow either case - it's all we can do. 368 */ 369 return 1; 370 } 371 if(cistrcmp(found, want) != 0) 372 return 0; 373 if(!Checkcase) 374 return 1; 375 if(strcmp(found, want) == 0) 376 return 1; 377 return 0; 378 } 379 380 381 static char* 382 fswalk1(Fid *fid, char *name, Qid *qid) 383 { 384 FInfo fi; 385 int rc, n, i; 386 Aux *a = fid->aux; 387 static char e[ERRMAX]; 388 char *p, *npath, *winpath; 389 390 *e = 0; 391 npath = newpath(a->path, name); 392 if(strcmp(npath, "/") == 0){ /* root dir */ 393 *qid = mkqid("/", 1, 1, Proot, 0); 394 free(a->path); 395 a->path = npath; 396 fid->qid = *qid; 397 return nil; 398 } 399 400 if(strrchr(npath, '/') == npath){ /* top level dir */ 401 if((n = walkinfo(name)) != -1){ /* info file */ 402 *qid = mkqid(npath, 0, 1, Pinfo, n); 403 } 404 else { /* volume name */ 405 for(i = 0; i < Nshares; i++){ 406 n = strlen(Shares[i].name); 407 if(cistrncmp(npath+1, Shares[i].name, n) != 0) 408 continue; 409 if(Checkcase && strncmp(npath+1, Shares[i].name, n) != 0) 410 continue; 411 if(npath[n+1] != 0 && npath[n+1] != '/') 412 continue; 413 break; 414 } 415 if(i >= Nshares){ 416 free(npath); 417 return "not found"; 418 } 419 a->sp = Shares+i; 420 *qid = mkqid(npath, 1, 1, Pshare, i); 421 } 422 free(a->path); 423 a->path = npath; 424 fid->qid = *qid; 425 return nil; 426 } 427 428 /* must be a vanilla file or directory */ 429 again: 430 if(mapshare(npath, &a->sp) == -1){ 431 rerrstr(e, sizeof(e)); 432 free(npath); 433 return e; 434 } 435 436 winpath = mapfile(npath); 437 memset(&fi, 0, sizeof fi); 438 if(Sess->caps & CAP_NT_SMBS) 439 rc = T2queryall(Sess, a->sp, winpath, &fi); 440 else 441 rc = T2querystandard(Sess, a->sp, winpath, &fi); 442 443 if(rc == -1){ 444 rerrstr(e, sizeof(e)); 445 free(npath); 446 return e; 447 } 448 449 if((a->sp->options & SMB_SHARE_IS_IN_DFS) != 0 && 450 (fi.attribs & ATTR_REPARSE) != 0){ 451 if(redirect(Sess, a->sp, npath) != -1) 452 goto again; 453 } 454 455 if((p = strrchr(fi.name, '/')) == nil && (p = strrchr(fi.name, '\\')) == nil) 456 p = fi.name; 457 else 458 p++; 459 460 if(! validfile(p, name, winpath, a->sp)){ 461 free(npath); 462 return "not found"; 463 464 } 465 *qid = mkqid(npath, fi.attribs & ATTR_DIRECTORY, fi.changed, 0, 0); 466 467 free(a->path); 468 a->path = npath; 469 fid->qid = *qid; 470 return nil; 471 } 472 473 static void 474 fsstat(Req *r) 475 { 476 int rc; 477 FInfo fi; 478 Aux *a = r->fid->aux; 479 480 if(ptype(r->fid->qid.path) == Proot) 481 V2D(&r->d, r->fid->qid, ""); 482 else if(ptype(r->fid->qid.path) == Pinfo) 483 dirgeninfo(pindex(r->fid->qid.path), &r->d); 484 else if(ptype(r->fid->qid.path) == Pshare) 485 V2D(&r->d, r->fid->qid, a->path +1); 486 else{ 487 memset(&fi, 0, sizeof fi); 488 if(Sess->caps & CAP_NT_SMBS) 489 rc = T2queryall(Sess, a->sp, mapfile(a->path), &fi); 490 else 491 rc = T2querystandard(Sess, a->sp, mapfile(a->path), &fi); 492 if(rc == -1){ 493 responderrstr(r); 494 return; 495 } 496 I2D(&r->d, a->sp, a->path, &fi); 497 if(Billtrog == 0) 498 upd_names(Sess, a->sp, mapfile(a->path), &r->d); 499 } 500 respond(r, nil); 501 } 502 503 static int 504 smbcreateopen(Aux *a, char *path, int mode, int perm, int is_create, 505 int is_dir, FInfo *fip) 506 { 507 int rc, action, attrs, access, result; 508 509 if(is_create && is_dir){ 510 if(CIFScreatedirectory(Sess, a->sp, path) == -1) 511 return -1; 512 return 0; 513 } 514 515 if(mode & DMAPPEND) { 516 werrstr("filesystem does not support DMAPPEND"); 517 return -1; 518 } 519 520 if(is_create) 521 action = 0x12; 522 else if(mode & OTRUNC) 523 action = 0x02; 524 else 525 action = 0x01; 526 527 if(perm & 0222) 528 attrs = ATTR_NORMAL; 529 else 530 attrs = ATTR_NORMAL|ATTR_READONLY; 531 532 switch (mode & OMASK){ 533 case OREAD: 534 access = 0; 535 break; 536 case OWRITE: 537 access = 1; 538 break; 539 case ORDWR: 540 access = 2; 541 break; 542 case OEXEC: 543 access = 3; 544 break; 545 default: 546 werrstr("%d bad open mode", mode & OMASK); 547 return -1; 548 break; 549 } 550 551 if(mode & DMEXCL == 0) 552 access |= 0x10; 553 else 554 access |= 0x40; 555 556 if((a->fh = CIFS_SMB_opencreate(Sess, a->sp, path, access, attrs, 557 action, &result)) == -1) 558 return -1; 559 560 if(Sess->caps & CAP_NT_SMBS) 561 rc = T2queryall(Sess, a->sp, mapfile(a->path), fip); 562 else 563 rc = T2querystandard(Sess, a->sp, mapfile(a->path), fip); 564 if(rc == -1){ 565 fprint(2, "internal error: stat of newly open/created file failed\n"); 566 return -1; 567 } 568 569 if((mode & OEXCL) && (result & 0x8000) == 0){ 570 werrstr("%d bad open mode", mode & OMASK); 571 return -1; 572 } 573 return 0; 574 } 575 576 /* Uncle Bill, you have a lot to answer for... */ 577 static int 578 ntcreateopen(Aux *a, char *path, int mode, int perm, int is_create, 579 int is_dir, FInfo *fip) 580 { 581 int options, result, attrs, flags, access, action, share; 582 583 if(mode & DMAPPEND){ 584 werrstr("CIFSopen, DMAPPEND not supported"); 585 return -1; 586 } 587 588 if(is_create){ 589 if(mode & OEXCL) 590 action = FILE_OPEN; 591 else if(mode & OTRUNC) 592 action = FILE_CREATE; 593 else 594 action = FILE_OVERWRITE_IF; 595 } else { 596 if(mode & OTRUNC) 597 action = FILE_OVERWRITE_IF; 598 else 599 action = FILE_OPEN_IF; 600 } 601 602 flags = 0; /* FIXME: really not sure */ 603 604 if(mode & OEXCL) 605 share = FILE_NO_SHARE; 606 else 607 share = FILE_SHARE_ALL; 608 609 switch (mode & OMASK){ 610 case OREAD: 611 access = GENERIC_READ; 612 break; 613 case OWRITE: 614 access = GENERIC_WRITE; 615 break; 616 case ORDWR: 617 access = GENERIC_ALL; 618 break; 619 case OEXEC: 620 access = GENERIC_EXECUTE; 621 break; 622 default: 623 werrstr("%d bad open mode", mode & OMASK); 624 return -1; 625 break; 626 } 627 628 if(is_dir){ 629 action = FILE_CREATE; 630 options = FILE_DIRECTORY_FILE; 631 if(perm & 0222) 632 attrs = ATTR_DIRECTORY; 633 else 634 attrs = ATTR_DIRECTORY|ATTR_READONLY; 635 } else { 636 options = FILE_NON_DIRECTORY_FILE; 637 if(perm & 0222) 638 attrs = ATTR_NORMAL; 639 else 640 attrs = ATTR_NORMAL|ATTR_READONLY; 641 } 642 643 if(mode & ORCLOSE){ 644 options |= FILE_DELETE_ON_CLOSE; 645 attrs |= ATTR_DELETE_ON_CLOSE; 646 } 647 648 if((a->fh = CIFS_NT_opencreate(Sess, a->sp, path, flags, options, 649 attrs, access, share, action, &result, fip)) == -1) 650 return -1; 651 652 if((mode & OEXCL) && (result & 0x8000) == 0){ 653 werrstr("%d bad open mode", mode & OMASK); 654 return -1; 655 } 656 657 return 0; 658 } 659 660 static void 661 fscreate(Req *r) 662 { 663 FInfo fi; 664 int rc, is_dir; 665 char *npath; 666 Aux *a = r->fid->aux; 667 668 a->end = a->off = 0; 669 a->cache = emalloc9p(max(Sess->mtu, MTU)); 670 671 is_dir = (r->ifcall.perm & DMDIR) == DMDIR; 672 npath = smprint("%s/%s", a->path, r->ifcall.name); 673 674 if(Sess->caps & CAP_NT_SMBS) 675 rc = ntcreateopen(a, mapfile(npath), r->ifcall.mode, 676 r->ifcall.perm, 1, is_dir, &fi); 677 else 678 rc = smbcreateopen(a, mapfile(npath), r->ifcall.mode, 679 r->ifcall.perm, 1, is_dir, &fi); 680 if(rc == -1){ 681 free(npath); 682 responderrstr(r); 683 return; 684 } 685 686 r->fid->qid = mkqid(npath, fi.attribs & ATTR_DIRECTORY, fi.changed, 0, 0); 687 688 r->ofcall.qid = r->fid->qid; 689 free(a->path); 690 a->path = npath; 691 692 respond(r, nil); 693 } 694 695 static void 696 fsopen(Req *r) 697 { 698 int rc; 699 FInfo fi; 700 Aux *a = r->fid->aux; 701 702 a->end = a->off = 0; 703 a->cache = emalloc9p(max(Sess->mtu, MTU)); 704 705 if(ptype(r->fid->qid.path) == Pinfo){ 706 if(makeinfo(pindex(r->fid->qid.path)) != -1) 707 respond(r, nil); 708 else 709 respond(r, "cannot generate info"); 710 return; 711 } 712 713 if(r->fid->qid.type & QTDIR){ 714 respond(r, nil); 715 return; 716 } 717 718 if(Sess->caps & CAP_NT_SMBS) 719 rc = ntcreateopen(a, mapfile(a->path), r->ifcall.mode, 0777, 720 0, 0, &fi); 721 else 722 rc = smbcreateopen(a, mapfile(a->path), r->ifcall.mode, 0777, 723 0, 0, &fi); 724 if(rc == -1){ 725 responderrstr(r); 726 return; 727 } 728 respond(r, nil); 729 } 730 731 static void 732 fswrite(Req *r) 733 { 734 vlong n, m, got; 735 Aux *a = r->fid->aux; 736 vlong len = r->ifcall.count; 737 vlong off = r->ifcall.offset; 738 char *buf = r->ifcall.data; 739 740 got = 0; 741 n = Sess->mtu -OVERHEAD; 742 do{ 743 if(len - got < n) 744 n = len - got; 745 m = CIFSwrite(Sess, a->sp, a->fh, off + got, buf + got, n); 746 if(m != -1) 747 got += m; 748 } while(got < len && m >= n); 749 750 r->ofcall.count = got; 751 if(m == -1) 752 responderrstr(r); 753 else 754 respond(r, nil); 755 } 756 757 static void 758 fsread(Req *r) 759 { 760 vlong n, m, got; 761 Aux *a = r->fid->aux; 762 char *buf = r->ofcall.data; 763 vlong len = r->ifcall.count; 764 vlong off = r->ifcall.offset; 765 766 if(ptype(r->fid->qid.path) == Pinfo){ 767 r->ofcall.count = readinfo(pindex(r->fid->qid.path), buf, len, 768 off); 769 respond(r, nil); 770 return; 771 } 772 773 if(r->fid->qid.type & QTDIR){ 774 dirread9p(r, dirgen, a); 775 respond(r, nil); 776 return; 777 } 778 779 got = 0; 780 n = Sess->mtu -OVERHEAD; 781 do{ 782 if(len - got < n) 783 n = len - got; 784 m = CIFSread(Sess, a->sp, a->fh, off + got, buf + got, n, len); 785 if(m != -1) 786 got += m; 787 } while(got < len && m >= n); 788 789 r->ofcall.count = got; 790 if(m == -1) 791 responderrstr(r); 792 else 793 respond(r, nil); 794 } 795 796 static void 797 fsdestroyfid(Fid *f) 798 { 799 Aux *a = f->aux; 800 801 if(ptype(f->qid.path) == Pinfo) 802 freeinfo(pindex(f->qid.path)); 803 f->omode = -1; 804 if(! a) 805 return; 806 if(a->fh != -1) 807 if(CIFSclose(Sess, a->sp, a->fh) == -1) 808 fprint(2, "%s: close failed fh=%d %r\n", argv0, a->fh); 809 if(a->sh != -1) 810 if(CIFSfindclose2(Sess, a->sp, a->sh) == -1) 811 fprint(2, "%s: findclose failed sh=%d %r\n", 812 argv0, a->sh); 813 if(a->path) 814 free(a->path); 815 if(a->cache) 816 free(a->cache); 817 818 if(a == Auxroot) 819 Auxroot = a->next; 820 a->prev->next = a->next; 821 a->next->prev = a->prev; 822 if(a->next == a->prev) 823 Auxroot = nil; 824 if(a) 825 free(a); 826 } 827 828 int 829 rdonly(Session *s, Share *sp, char *path, int rdonly) 830 { 831 int rc; 832 FInfo fi; 833 834 if(Sess->caps & CAP_NT_SMBS) 835 rc = T2queryall(s, sp, path, &fi); 836 else 837 rc = T2querystandard(s, sp, path, &fi); 838 if(rc == -1) 839 return -1; 840 841 if((rdonly && !(fi.attribs & ATTR_READONLY)) || 842 (!rdonly && (fi.attribs & ATTR_READONLY))){ 843 fi.attribs &= ~ATTR_READONLY; 844 fi.attribs |= rdonly? ATTR_READONLY: 0; 845 rc = CIFSsetinfo(s, sp, path, &fi); 846 } 847 return rc; 848 } 849 850 static void 851 fsremove(Req *r) 852 { 853 int try, rc; 854 char e[ERRMAX]; 855 Aux *ap, *a = r->fid->aux; 856 857 *e = 0; 858 if(ptype(r->fid->qid.path) == Proot || 859 ptype(r->fid->qid.path) == Pshare){ 860 respond(r, "illegal operation"); 861 return; 862 } 863 864 /* close all instences of this file/dir */ 865 if((ap = Auxroot) != nil) 866 do{ 867 if(strcmp(ap->path, a->path) == 0){ 868 if(ap->sh != -1) 869 CIFSfindclose2(Sess, ap->sp, ap->sh); 870 ap->sh = -1; 871 if(ap->fh != -1) 872 CIFSclose(Sess, ap->sp, ap->fh); 873 ap->fh = -1; 874 } 875 ap = ap->next; 876 }while(ap != Auxroot); 877 try = 0; 878 again: 879 if(r->fid->qid.type & QTDIR) 880 rc = CIFSdeletedirectory(Sess, a->sp, mapfile(a->path)); 881 else 882 rc = CIFSdeletefile(Sess, a->sp, mapfile(a->path)); 883 884 rerrstr(e, sizeof(e)); 885 if(rc == -1 && try++ == 0 && strcmp(e, "permission denied") == 0 && 886 rdonly(Sess, a->sp, mapfile(a->path), 0) == 0) 887 goto again; 888 if(rc == -1) 889 responderrstr(r); 890 else 891 respond(r, nil); 892 } 893 894 static void 895 fswstat(Req *r) 896 { 897 int fh, result, rc; 898 FInfo fi, tmpfi; 899 char *p, *from, *npath; 900 Aux *a = r->fid->aux; 901 902 if(ptype(r->fid->qid.path) == Proot || 903 ptype(r->fid->qid.path) == Pshare){ 904 respond(r, "illegal operation"); 905 return; 906 } 907 908 if((r->d.uid && r->d.uid[0]) || (r->d.gid && r->d.gid[0])){ 909 respond(r, "cannot change ownership"); 910 return; 911 } 912 913 /* 914 * get current info 915 */ 916 if(Sess->caps & CAP_NT_SMBS) 917 rc = T2queryall(Sess, a->sp, mapfile(a->path), &fi); 918 else 919 rc = T2querystandard(Sess, a->sp, mapfile(a->path), &fi); 920 if(rc == -1){ 921 werrstr("(query) - %r"); 922 responderrstr(r); 923 return; 924 } 925 926 /* 927 * always clear the readonly attribute if set, 928 * before trying to set any other fields. 929 * wstat() fails if the file/dir is readonly 930 * and this function is so full of races - who cares about one more? 931 */ 932 rdonly(Sess, a->sp, mapfile(a->path), 0); 933 934 /* 935 * rename - one piece of joy, renaming open files 936 * is legal (sharing permitting). 937 */ 938 if(r->d.name && r->d.name[0]){ 939 if((p = strrchr(a->path, '/')) == nil){ 940 respond(r, "illegal path"); 941 return; 942 } 943 npath = emalloc9p((p-a->path)+strlen(r->d.name)+2); 944 strecpy(npath, npath+(p- a->path)+2, a->path); 945 strcat(npath, r->d.name); 946 947 from = estrdup9p(mapfile(a->path)); 948 if(CIFSrename(Sess, a->sp, from, mapfile(npath)) == -1){ 949 werrstr("(rename) - %r"); 950 responderrstr(r); 951 free(npath); 952 free(from); 953 return; 954 } 955 free(from); 956 free(a->path); 957 a->path = npath; 958 } 959 960 /* 961 * set the files length, do this before setting 962 * the file times as open() will alter them 963 */ 964 if(~r->d.length){ 965 fi.size = r->d.length; 966 967 if(Sess->caps & CAP_NT_SMBS){ 968 if((fh = CIFS_NT_opencreate(Sess, a->sp, mapfile(a->path), 969 0, FILE_NON_DIRECTORY_FILE, 970 ATTR_NORMAL, GENERIC_WRITE, FILE_SHARE_ALL, 971 FILE_OPEN_IF, &result, &tmpfi)) == -1){ 972 werrstr("(set length, open) - %r"); 973 responderrstr(r); 974 return; 975 } 976 rc = T2setfilelength(Sess, a->sp, fh, &fi); 977 CIFSclose(Sess, a->sp, fh); 978 if(rc == -1){ 979 werrstr("(set length), set) - %r"); 980 responderrstr(r); 981 return; 982 } 983 } else { 984 if((fh = CIFS_SMB_opencreate(Sess, a->sp, mapfile(a->path), 985 1, ATTR_NORMAL, 1, &result)) == -1){ 986 werrstr("(set length, open) failed - %r"); 987 responderrstr(r); 988 return; 989 } 990 rc = CIFSwrite(Sess, a->sp, fh, fi.size, 0, 0); 991 CIFSclose(Sess, a->sp, fh); 992 if(rc == -1){ 993 werrstr("(set length, write) - %r"); 994 responderrstr(r); 995 return; 996 } 997 } 998 } 999 1000 /* 1001 * This doesn't appear to set length or 1002 * attributes, no idea why, so I do those seperately 1003 */ 1004 if(~r->d.mtime || ~r->d.atime){ 1005 if(~r->d.mtime) 1006 fi.written = r->d.mtime; 1007 if(~r->d.atime) 1008 fi.accessed = r->d.atime; 1009 if(T2setpathinfo(Sess, a->sp, mapfile(a->path), &fi) == -1){ 1010 werrstr("(set path info) - %r"); 1011 responderrstr(r); 1012 return; 1013 } 1014 } 1015 1016 /* 1017 * always update the readonly flag as 1018 * we may have cleared it above. 1019 */ 1020 if(~r->d.mode){ 1021 if(r->d.mode & 0222) 1022 fi.attribs &= ~ATTR_READONLY; 1023 else 1024 fi.attribs |= ATTR_READONLY; 1025 } 1026 if(rdonly(Sess, a->sp, mapfile(a->path), fi.attribs & ATTR_READONLY) == -1){ 1027 werrstr("(set info) - %r"); 1028 responderrstr(r); 1029 return; 1030 } 1031 1032 /* 1033 * Win95 has a broken write-behind cache for metadata 1034 * on open files (writes go to the cache, reads bypass 1035 * the cache), so we must flush the file. 1036 */ 1037 if(r->fid->omode != -1 && CIFSflush(Sess, a->sp, a->fh) == -1){ 1038 werrstr("(flush) %r"); 1039 responderrstr(r); 1040 return; 1041 } 1042 respond(r, nil); 1043 } 1044 1045 static void 1046 fsend(Srv *srv) 1047 { 1048 int i; 1049 USED(srv); 1050 1051 for(i = 0; i < Nshares; i++) 1052 CIFStreedisconnect(Sess, Shares+i); 1053 CIFSlogoff(Sess); 1054 postnote(PNPROC, Keeppid, "die"); 1055 } 1056 1057 Srv fs = { 1058 .destroyfid = fsdestroyfid, 1059 .attach= fsattach, 1060 .open= fsopen, 1061 .create= fscreate, 1062 .read= fsread, 1063 .write= fswrite, 1064 .remove= fsremove, 1065 .stat= fsstat, 1066 .wstat= fswstat, 1067 .clone= fsclone, 1068 .walk1= fswalk1, 1069 .end= fsend, 1070 }; 1071 1072 void 1073 usage(void) 1074 { 1075 fprint(2, "usage: %s [-d name] [-Dvb] [-a auth-method] [-s srvname] " 1076 "[-n called-name] [-k factotum-params] [-m mntpnt] " 1077 "host [share...]\n", argv0); 1078 exits("usage"); 1079 } 1080 1081 /* 1082 * SMBecho looks like the function to use for keepalives, 1083 * sadly the echo packet does not seem to reload the 1084 * idle timer in Microsoft's servers. Instead we use 1085 * "get file system size" on each share until we get one that succeeds. 1086 */ 1087 static void 1088 keepalive(void) 1089 { 1090 char buf[32]; 1091 uvlong tot, fre; 1092 int fd, i, slot, rc; 1093 1094 snprint(buf, sizeof buf, "#p/%d/args", getpid()); 1095 if((fd = open(buf, OWRITE)) >= 0){ 1096 fprint(fd, "%s keepalive", Host); 1097 close(fd); 1098 } 1099 1100 rc = 0; 1101 slot = 0; 1102 do{ 1103 sleep(6000); 1104 if(Active-- != 0) 1105 continue; 1106 for(i = 0; i < Nshares; i++){ 1107 if((rc = T2fssizeinfo(Sess, &Shares[slot], &tot, &fre)) == 0) 1108 break; 1109 if(++slot >= Nshares) 1110 slot = 0; 1111 } 1112 }while(rc != -1); 1113 postnote(PNPROC, Attachpid, "die"); 1114 } 1115 1116 1117 static void 1118 ding(void *u, char *msg) 1119 { 1120 USED(u); 1121 if(strstr(msg, "alarm") != nil) 1122 noted(NCONT); 1123 noted(NDFLT); 1124 } 1125 1126 void 1127 dmpkey(char *s, void *v, int n) 1128 { 1129 int i; 1130 unsigned char *p = (unsigned char *)v; 1131 1132 print("%s", s); 1133 for(i = 0; i < n; i++) 1134 print("%02ux ", *p++); 1135 print("\n"); 1136 } 1137 1138 void 1139 main(int argc, char **argv) 1140 { 1141 int i, n; 1142 long svrtime; 1143 char windom[64], cname[64]; 1144 char *method, *sysname, *keyp, *mtpt, *svs; 1145 static char *sh[1024]; 1146 1147 *cname = 0; 1148 keyp = ""; 1149 method = nil; 1150 strcpy(windom, "unknown"); 1151 mtpt = svs = nil; 1152 1153 notify(ding); 1154 1155 ARGBEGIN{ 1156 case 'a': 1157 method = EARGF(autherr()); 1158 break; 1159 case 'b': 1160 Billtrog ^= 1; 1161 break; 1162 case 'D': 1163 chatty9p++; 1164 break; 1165 case 'd': 1166 Debug = EARGF(usage()); 1167 break; 1168 case 'i': 1169 Checkcase = 0; 1170 break; 1171 case 'k': 1172 keyp = EARGF(usage()); 1173 break; 1174 case 'm': 1175 mtpt = EARGF(usage()); 1176 break; 1177 case 'n': 1178 strncpy(cname, EARGF(usage()), sizeof(cname)); 1179 cname[sizeof(cname) - 1] = 0; 1180 break; 1181 case 's': 1182 svs = EARGF(usage()); 1183 break; 1184 case 't': 1185 Dfstout = atoi(EARGF(usage())); 1186 break; 1187 default: 1188 usage(); 1189 break; 1190 }ARGEND 1191 1192 if(argc < 1) 1193 usage(); 1194 1195 Host = argv[0]; 1196 1197 if(mtpt == nil && svs == nil) 1198 mtpt = smprint("/n/%s", Host); 1199 1200 if((sysname = getenv("sysname")) == nil) 1201 sysname = "unknown"; 1202 1203 if(*cname && (Sess = cifsdial(Host, cname, sysname)) != nil) 1204 goto connected; 1205 1206 if(calledname(Host, cname) == 0 && 1207 (Sess = cifsdial(Host, cname, sysname)) != nil) 1208 goto connected; 1209 1210 strcpy(cname, Host); 1211 if((Sess = cifsdial(Host, Host, sysname)) != nil || 1212 (Sess = cifsdial(Host, "*SMBSERVER", sysname)) != nil) 1213 goto connected; 1214 1215 sysfatal("%s - cannot dial, %r\n", Host); 1216 connected: 1217 if(CIFSnegotiate(Sess, &svrtime, windom, sizeof windom, cname, sizeof cname) == -1) 1218 sysfatal("%s - cannot negioate common protocol, %r\n", Host); 1219 1220 #ifndef DEBUG_MAC 1221 Sess->secmode &= ~SECMODE_SIGN_ENABLED; 1222 #endif 1223 1224 Sess->auth = getauth(method, windom, keyp, Sess->secmode, Sess->chal, 1225 Sess->challen); 1226 1227 if(CIFSsession(Sess) < 0) 1228 sysfatal("session authentication failed, %r\n"); 1229 1230 Sess->slip = svrtime - time(nil); 1231 Sess->cname = estrdup9p(cname); 1232 1233 if(CIFStreeconnect(Sess, cname, Ipcname, &Ipc) == -1) 1234 fprint(2, "%s, %r - can't connect\n", Ipcname); 1235 1236 Nshares = 0; 1237 if(argc == 1){ 1238 Share *sip; 1239 1240 if((n = RAPshareenum(Sess, &Ipc, &sip)) < 1) 1241 sysfatal("can't enumerate shares: %r - specify share " 1242 "names on command line\n"); 1243 1244 for(i = 0; i < n; i++){ 1245 #ifdef NO_HIDDEN_SHARES 1246 int l = strlen(sip[i].name); 1247 1248 if(l > 1 && sip[i].name[l-1] == '$'){ 1249 free(sip[i].name); 1250 continue; 1251 } 1252 #endif 1253 memcpy(Shares+Nshares, sip+i, sizeof(Share)); 1254 if(CIFStreeconnect(Sess, Sess->cname, 1255 Shares[Nshares].name, Shares+Nshares) == -1){ 1256 free(Shares[Nshares].name); 1257 continue; 1258 } 1259 Nshares++; 1260 } 1261 free(sip); 1262 } else 1263 for(i = 1; i < argc; i++){ 1264 if(CIFStreeconnect(Sess, Sess->cname, argv[i], 1265 Shares+Nshares) == -1){ 1266 fprint(2, "%s: %s %q - can't connect to share" 1267 ", %r\n", argv0, Host, argv[i]); 1268 continue; 1269 } 1270 Shares[Nshares].name = strlwr(estrdup9p(argv[i])); 1271 Nshares++; 1272 } 1273 1274 if(Nshares == 0) 1275 fprint(2, "no available shares\n"); 1276 1277 if((Keeppid = rfork(RFPROC|RFMEM|RFNOTEG|RFFDG|RFNAMEG)) == 0){ 1278 keepalive(); 1279 exits(nil); 1280 } 1281 postmountsrv(&fs, svs, mtpt, MREPL|MCREATE); 1282 exits(nil); 1283 } 1284