1 /* 2 * tftpd - tftp service, see /lib/rfc/rfc783 (now rfc1350 + 234[789]) 3 */ 4 #include <u.h> 5 #include <libc.h> 6 #include <auth.h> 7 #include <bio.h> 8 #include <ip.h> 9 #include <ndb.h> 10 11 enum 12 { 13 Maxpath= 128, 14 Maxerr= 256, 15 16 Debug= 0, 17 18 Opsize= sizeof(short), 19 Blksize= sizeof(short), 20 Hdrsize= Opsize + Blksize, 21 22 Ackerr= -1, 23 Ackok= 0, 24 Ackrexmit= 1, 25 26 /* op codes */ 27 Tftp_READ = 1, 28 Tftp_WRITE = 2, 29 Tftp_DATA = 3, 30 Tftp_ACK = 4, 31 Tftp_ERROR = 5, 32 Tftp_OACK = 6, /* option acknowledge */ 33 34 Errnotdef = 0, /* see textual error instead */ 35 Errnotfound = 1, 36 Errnoaccess = 2, 37 Errdiskfull = 3, 38 Errbadop = 4, 39 Errbadtid = 5, 40 Errexists = 6, 41 Errnouser = 7, 42 Errbadopt = 8, /* really bad option value */ 43 44 Defsegsize = 512, 45 Maxsegsize = 65464, /* from rfc2348 */ 46 47 /* 48 * bandt (viaduct) tunnels use smaller mtu than ether's 49 * (1400 bytes for tcp mss of 1300 bytes). 50 */ 51 Bandtmtu = 1400, 52 /* 53 * maximum size of block's data content, excludes hdrs, 54 * notably IP/UDP and TFTP, using worst-case (IPv6) sizes. 55 */ 56 Bandtblksz = Bandtmtu - 40 - 8, 57 Bcavium = 1432, /* cavium's u-boot demands this size */ 58 }; 59 60 typedef struct Opt Opt; 61 struct Opt { 62 char *name; 63 int *valp; /* set to client's value if within bounds */ 64 int min; 65 int max; 66 }; 67 68 int dbg; 69 int restricted; 70 int pid; 71 72 /* options */ 73 int blksize = Defsegsize; /* excluding 4-byte header */ 74 int timeout = 5; /* seconds */ 75 int tsize; 76 static Opt option[] = { 77 "timeout", &timeout, 1, 255, 78 /* see "hack" below */ 79 "blksize", &blksize, 8, Maxsegsize, 80 "tsize", &tsize, 0, ~0UL >> 1, 81 }; 82 83 void sendfile(int, char*, char*, int); 84 void recvfile(int, char*, char*); 85 void nak(int, int, char*); 86 void ack(int, ushort); 87 void clrcon(void); 88 void setuser(void); 89 char* sunkernel(char*); 90 void remoteaddr(char*, char*, int); 91 void doserve(int); 92 93 char bigbuf[32768]; 94 char raddr[64]; 95 96 char *dir = "/lib/tftpd"; 97 char *dirsl; 98 int dirsllen; 99 char flog[] = "ipboot"; 100 char net[Maxpath]; 101 102 static char *opnames[] = { 103 [Tftp_READ] "read", 104 [Tftp_WRITE] "write", 105 [Tftp_DATA] "data", 106 [Tftp_ACK] "ack", 107 [Tftp_ERROR] "error", 108 [Tftp_OACK] "oack", 109 }; 110 111 void 112 usage(void) 113 { 114 fprint(2, "usage: %s [-dr] [-h homedir] [-s svc] [-x netmtpt]\n", 115 argv0); 116 exits("usage"); 117 } 118 119 void 120 main(int argc, char **argv) 121 { 122 char buf[64]; 123 char adir[64], ldir[64]; 124 int cfd, lcfd, dfd; 125 char *svc = "69"; 126 127 setnetmtpt(net, sizeof net, nil); 128 ARGBEGIN{ 129 case 'd': 130 dbg++; 131 break; 132 case 'h': 133 dir = EARGF(usage()); 134 break; 135 case 'r': 136 restricted = 1; 137 break; 138 case 's': 139 svc = EARGF(usage()); 140 break; 141 case 'x': 142 setnetmtpt(net, sizeof net, EARGF(usage())); 143 break; 144 default: 145 usage(); 146 }ARGEND 147 148 snprint(buf, sizeof buf, "%s/", dir); 149 dirsl = strdup(buf); 150 dirsllen = strlen(dirsl); 151 152 fmtinstall('E', eipfmt); 153 fmtinstall('I', eipfmt); 154 155 /* 156 * setuser calls newns, and typical /lib/namespace files contain 157 * "cd /usr/$user", so call setuser before chdir. 158 */ 159 setuser(); 160 if(chdir(dir) < 0) 161 sysfatal("can't get to directory %s: %r", dir); 162 163 if(!dbg) 164 switch(rfork(RFNOTEG|RFPROC|RFFDG)) { 165 case -1: 166 sysfatal("fork: %r"); 167 case 0: 168 break; 169 default: 170 exits(0); 171 } 172 173 snprint(buf, sizeof buf, "%s/udp!*!%s", net, svc); 174 cfd = announce(buf, adir); 175 if (cfd < 0) 176 sysfatal("announcing on %s: %r", buf); 177 syslog(dbg, flog, "tftpd started on %s dir %s", buf, adir); 178 // setuser(); 179 for(;;) { 180 lcfd = listen(adir, ldir); 181 if(lcfd < 0) 182 sysfatal("listening on %s: %r", adir); 183 184 switch(fork()) { 185 case -1: 186 sysfatal("fork: %r"); 187 case 0: 188 dfd = accept(lcfd, ldir); 189 if(dfd < 0) 190 exits(0); 191 remoteaddr(ldir, raddr, sizeof(raddr)); 192 pid = getpid(); 193 syslog(0, flog, "tftp %d connection from %s dir %s", 194 pid, raddr, ldir); 195 doserve(dfd); 196 exits("done"); 197 break; 198 default: 199 close(lcfd); 200 continue; 201 } 202 } 203 } 204 205 static Opt * 206 handleopt(int fd, char *name, char *val) 207 { 208 int n; 209 Opt *op; 210 211 for (op = option; op < option + nelem(option); op++) 212 if(cistrcmp(name, op->name) == 0) { 213 n = strtol(val, nil, 10); 214 if (n < op->min || n > op->max) { 215 nak(fd, Errbadopt, "option value out of range"); 216 syslog(dbg, flog, "tftp bad option value from " 217 "client: %s %s", name, val); 218 sysfatal("bad option value from client: %s %s", 219 name, val); 220 } 221 *op->valp = n; 222 /* incoming 0 for tsize is uninteresting */ 223 if(cistrcmp("tsize", op->name) != 0) 224 syslog(dbg, flog, "tftpd %d setting %s to client's %d", 225 pid, name, n); 226 return op; 227 } 228 return nil; 229 } 230 231 static vlong 232 filesize(char *file) 233 { 234 vlong size; 235 Dir *dp; 236 237 dp = dirstat(file); 238 if (dp == nil) 239 return -1; 240 size = dp->length; 241 free(dp); 242 return size; 243 } 244 245 /* copy word into bp iff it fits before ep, returns bytes to advance bp. */ 246 static int 247 emits(char *word, char *bp, char *ep) 248 { 249 int len; 250 251 len = strlen(word) + 1; 252 if (bp + len >= ep) 253 return -1; 254 strcpy(bp, word); 255 return len; 256 } 257 258 /* format number into bp iff it fits before ep. */ 259 static int 260 emitn(vlong n, char *bp, char *ep) 261 { 262 char numb[32]; 263 264 snprint(numb, sizeof numb, "%lld", n); 265 return emits(numb, bp, ep); 266 } 267 268 /* 269 * send an OACK packet to respond to options. bail early with -1 on error. 270 * p is the packet containing the options. 271 * 272 * hack: bandt (viaducts) uses smaller mtu than ether's 273 * (1400 bytes for tcp mss of 1300 bytes), 274 * so offer at most bandt's mtu minus headers, 275 * to avoid failure of pxe booting via viaduct. 276 * there's an exception for the cavium's u-boot. 277 */ 278 static int 279 options(int fd, char *buf, int bufsz, char *file, ushort oper, char *p, int dlen) 280 { 281 int nmlen, vallen, olen, nopts; 282 vlong size; 283 char *val, *bp, *ep; 284 Opt *op; 285 286 buf[0] = 0; 287 buf[1] = Tftp_OACK; 288 bp = buf + Opsize; 289 ep = buf + bufsz; 290 nopts = 0; 291 for (; dlen > 0 && *p != '\0'; p = val + vallen, bp += olen) { 292 nmlen = strlen(p) + 1; /* include NUL */ 293 if (nmlen > dlen) 294 break; 295 dlen -= nmlen; 296 val = p + nmlen; 297 if (dlen <= 0 || *val == '\0') 298 break; 299 300 vallen = strlen(val) + 1; 301 if (vallen > dlen) 302 break; 303 dlen -= vallen; 304 305 nopts++; 306 olen = 0; 307 op = handleopt(fd, p, val); 308 if (op == nil) 309 continue; 310 311 /* append OACK response to buf */ 312 nmlen = emits(p, bp, ep); /* option name */ 313 if (nmlen < 0) 314 return -1; 315 bp += nmlen; 316 317 if (oper == Tftp_READ && cistrcmp(p, "tsize") == 0) { 318 size = filesize(file); 319 if (size == -1) { 320 nak(fd, Errnotfound, "no such file"); 321 syslog(dbg, flog, "tftpd tsize for " 322 "non-existent file %s", file); 323 // *op->valp = 0; 324 // olen = emits("0", bp, ep); 325 return -1; 326 } 327 *op->valp = size; 328 olen = emitn(size, bp, ep); 329 syslog(dbg, flog, "tftpd %d %s tsize is %,lld", 330 pid, file, size); 331 } else if (oper == Tftp_READ && cistrcmp(p, "blksize") == 0 && 332 blksize > Bandtblksz && blksize != Bcavium) { 333 *op->valp = blksize = Bandtblksz; 334 olen = emitn(blksize, bp, ep); 335 syslog(dbg, flog, "tftpd %d overriding blksize to %d", 336 pid, blksize); 337 } else 338 olen = emits(val, bp, ep); /* use requested value */ 339 } 340 if (nopts == 0) 341 return 0; /* no options actually seen */ 342 343 if (write(fd, buf, bp - buf) < bp - buf) { 344 syslog(dbg, flog, "tftpd network write error on oack to %s: %r", 345 raddr); 346 sysfatal("tftpd: network write error: %r"); 347 } 348 if(Debug) 349 syslog(dbg, flog, "tftpd oack: options to %s", raddr); 350 return nopts; 351 } 352 353 static void 354 optlog(char *bytes, char *p, int dlen) 355 { 356 char *bp; 357 358 bp = bytes; 359 sprint(bp, "tftpd %d option bytes: ", dlen); 360 bp += strlen(bp); 361 for (; dlen > 0; dlen--, p++) 362 *bp++ = *p? *p: ' '; 363 *bp = '\0'; 364 syslog(dbg, flog, "%s", bytes); 365 } 366 367 /* 368 * replace one occurrence of %[ICE] with ip, cfgpxe name, or ether mac, resp. 369 * we can't easily use $ because u-boot has stranger quoting rules than sh. 370 */ 371 char * 372 mapname(char *file) 373 { 374 int nf; 375 char *p, *newnm, *cur, *arpf, *ln, *remip, *bang; 376 char *fields[4]; 377 Biobuf *arp; 378 379 p = strchr(file, '%'); 380 if (p == nil || p[1] == '\0') 381 return strdup(file); 382 383 remip = strdup(raddr); 384 newnm = mallocz(strlen(file) + Maxpath, 1); 385 if (remip == nil || newnm == nil) 386 sysfatal("out of memory"); 387 388 bang = strchr(remip, '!'); 389 if (bang) 390 *bang = '\0'; /* remove !port */ 391 392 memmove(newnm, file, p - file); /* copy up to % */ 393 cur = newnm + strlen(newnm); 394 switch(p[1]) { 395 case 'I': 396 strcpy(cur, remip); /* remote's IP */ 397 break; 398 case 'C': 399 strcpy(cur, "/cfg/pxe/"); 400 cur += strlen(cur); 401 /* fall through */ 402 case 'E': 403 /* look up remote's IP in /net/arp to get mac. */ 404 arpf = smprint("%s/arp", net); 405 arp = Bopen(arpf, OREAD); 406 free(arpf); 407 if (arp == nil) 408 break; 409 /* read lines looking for remip in 3rd field of 4 */ 410 while ((ln = Brdline(arp, '\n')) != nil) { 411 ln[Blinelen(arp)-1] = 0; 412 nf = tokenize(ln, fields, nelem(fields)); 413 if (nf >= 4 && strcmp(fields[2], remip) == 0) { 414 strcpy(cur, fields[3]); 415 break; 416 } 417 } 418 Bterm(arp); 419 break; 420 } 421 strcat(newnm, p + 2); /* tail following %x */ 422 free(remip); 423 return newnm; 424 } 425 426 void 427 doserve(int fd) 428 { 429 int dlen, opts; 430 char *mode, *p, *file; 431 short op; 432 433 dlen = read(fd, bigbuf, sizeof(bigbuf)-1); 434 if(dlen < 0) 435 sysfatal("listen read: %r"); 436 437 bigbuf[dlen] = '\0'; 438 op = (bigbuf[0]<<8) | bigbuf[1]; 439 dlen -= Opsize; 440 mode = file = bigbuf + Opsize; 441 while(*mode != '\0' && dlen--) 442 mode++; 443 mode++; 444 p = mode; 445 while(*p && dlen--) 446 p++; 447 448 file = mapname(file); /* we don't free the result; minor leak */ 449 450 if(dlen == 0) { 451 nak(fd, 0, "bad tftpmode"); 452 close(fd); 453 syslog(dbg, flog, "tftpd %d bad mode %s for file %s from %s", 454 pid, mode, file, raddr); 455 return; 456 } 457 458 if(op != Tftp_READ && op != Tftp_WRITE) { 459 nak(fd, Errbadop, "Illegal TFTP operation"); 460 close(fd); 461 syslog(dbg, flog, "tftpd %d bad request %d (%s) %s", pid, op, 462 (op < nelem(opnames)? opnames[op]: "gok"), raddr); 463 return; 464 } 465 466 if(restricted){ 467 if(file[0] == '#' || strncmp(file, "../", 3) == 0 || 468 strstr(file, "/../") != nil || 469 (file[0] == '/' && strncmp(file, dirsl, dirsllen) != 0)){ 470 nak(fd, Errnoaccess, "Permission denied"); 471 close(fd); 472 syslog(dbg, flog, "tftpd %d bad request %d from %s file %s", 473 pid, op, raddr, file); 474 return; 475 } 476 } 477 478 /* 479 * options are supposed to be negotiated, but the cavium board's 480 * u-boot really wants us to use a block size of 1432 bytes and won't 481 * take `no' for an answer. 482 */ 483 p++; /* skip NUL after mode */ 484 dlen--; 485 opts = 0; 486 if(dlen > 0) { /* might have options */ 487 char bytes[32*1024]; 488 489 if(Debug) 490 optlog(bytes, p, dlen); 491 opts = options(fd, bytes, sizeof bytes, file, op, p, dlen); 492 if (opts < 0) 493 return; 494 } 495 if(op == Tftp_READ) 496 sendfile(fd, file, mode, opts); 497 else 498 recvfile(fd, file, mode); 499 } 500 501 void 502 catcher(void *junk, char *msg) 503 { 504 USED(junk); 505 506 if(strncmp(msg, "exit", 4) == 0) 507 noted(NDFLT); 508 noted(NCONT); 509 } 510 511 static int 512 awaitack(int net, int block) 513 { 514 int ackblock, al, rxl; 515 ushort op; 516 uchar ack[1024]; 517 518 for(rxl = 0; rxl < 10; rxl++) { 519 memset(ack, 0, Hdrsize); 520 alarm(1000); 521 al = read(net, ack, sizeof(ack)); 522 alarm(0); 523 if(al < 0) { 524 if (Debug) 525 syslog(dbg, flog, "tftpd %d timed out " 526 "waiting for ack from %s", pid, raddr); 527 return Ackrexmit; 528 } 529 op = ack[0]<<8|ack[1]; 530 if(op == Tftp_ERROR) { 531 if (Debug) 532 syslog(dbg, flog, "tftpd %d got error " 533 "waiting for ack from %s", pid, raddr); 534 return Ackerr; 535 } else if(op != Tftp_ACK) { 536 syslog(dbg, flog, "tftpd %d rcvd %s op from %s", pid, 537 (op < nelem(opnames)? opnames[op]: "gok"), 538 raddr); 539 return Ackerr; 540 } 541 ackblock = ack[2]<<8|ack[3]; 542 if (Debug) 543 syslog(dbg, flog, "tftpd %d read ack of %d bytes " 544 "for block %d", pid, al, ackblock); 545 if(ackblock == block) 546 return Ackok; /* for block just sent */ 547 else if(ackblock == block + 1) /* intel pxe eof bug */ 548 return Ackok; 549 else if(ackblock == 0xffff) 550 return Ackrexmit; 551 else 552 /* ack is for some other block; ignore it, try again */ 553 syslog(dbg, flog, "tftpd %d expected ack for block %d, " 554 "got %d", pid, block, ackblock); 555 } 556 return Ackrexmit; 557 } 558 559 void 560 sendfile(int net, char *name, char *mode, int opts) 561 { 562 int file, block, ret, rexmit, n, txtry, failed; 563 uchar buf[Maxsegsize+Hdrsize]; 564 char errbuf[Maxerr]; 565 566 file = -1; 567 failed = 1; 568 syslog(dbg, flog, "tftpd %d send file '%s' %s to %s", 569 pid, name, mode, raddr); 570 name = sunkernel(name); 571 if(name == 0){ 572 nak(net, 0, "not in our database"); 573 goto error; 574 } 575 576 notify(catcher); 577 578 file = open(name, OREAD); 579 if(file < 0) { 580 errstr(errbuf, sizeof errbuf); 581 nak(net, 0, errbuf); 582 goto error; 583 } 584 block = 0; 585 rexmit = Ackok; 586 n = 0; 587 /* 588 * if we sent an oack previously, wait for the client's ack or error. 589 * if we get no ack for our oack, it could be that we returned 590 * a tsize that the client can't handle, or it could be intel 591 * pxe just read-with-tsize to get size, couldn't be bothered to 592 * ack our oack and has just gone ahead and issued another read. 593 */ 594 if(opts && awaitack(net, 0) != Ackok) 595 goto error; 596 597 for(txtry = 0; txtry < timeout;) { 598 if(rexmit == Ackok) { 599 /* block number wraparound for enormous hogs */ 600 if (block >= 65536) 601 block = 0; 602 block++; 603 buf[0] = 0; 604 buf[1] = Tftp_DATA; 605 buf[2] = block>>8; 606 buf[3] = block; 607 n = read(file, buf+Hdrsize, blksize); 608 if(n < 0) { 609 errstr(errbuf, sizeof errbuf); 610 nak(net, 0, errbuf); 611 goto error; 612 } 613 txtry = 0; 614 } 615 else { 616 syslog(dbg, flog, "tftpd %d rexmit %d %s:%d to %s", 617 pid, Hdrsize+n, name, block, raddr); 618 txtry++; 619 } 620 621 ret = write(net, buf, Hdrsize+n); 622 if(ret < Hdrsize+n) { 623 syslog(dbg, flog, 624 "tftpd network write error on %s to %s: %r", 625 name, raddr); 626 sysfatal("tftpd: network write error: %r"); 627 } 628 if (Debug) 629 syslog(dbg, flog, "tftpd %d sent block %d", pid, block); 630 631 rexmit = awaitack(net, block); 632 if (rexmit == Ackerr) 633 break; 634 if(ret != blksize+Hdrsize && rexmit == Ackok) { 635 failed = 0; 636 break; 637 } 638 } 639 error: 640 syslog(dbg, flog, "tftpd %d %s file '%s' %s to %s", 641 pid, (failed? "failed to send": "sent"), name, mode, raddr); 642 close(net); 643 close(file); 644 } 645 646 void 647 recvfile(int net, char *name, char *mode) 648 { 649 ushort op, block, inblock; 650 uchar buf[Maxsegsize+8]; 651 char errbuf[Maxerr]; 652 int n, ret, file; 653 654 syslog(dbg, flog, "receive file '%s' %s from %s", name, mode, raddr); 655 656 file = create(name, OWRITE, 0666); 657 if(file < 0) { 658 errstr(errbuf, sizeof errbuf); 659 nak(net, 0, errbuf); 660 syslog(dbg, flog, "can't create %s: %r", name); 661 return; 662 } 663 664 block = 0; 665 ack(net, block); 666 block++; 667 668 for (;;) { 669 alarm(15000); 670 n = read(net, buf, blksize+8); 671 alarm(0); 672 if(n < 0) { 673 syslog(dbg, flog, "tftpd: network error reading %s: %r", 674 name); 675 goto error; 676 } 677 /* 678 * NB: not `<='; just a header is legal and happens when 679 * file being read is a multiple of segment-size bytes long. 680 */ 681 if(n < Hdrsize) { 682 syslog(dbg, flog, 683 "tftpd: short read from network, reading %s", 684 name); 685 goto error; 686 } 687 op = buf[0]<<8|buf[1]; 688 if(op == Tftp_ERROR) { 689 syslog(dbg, flog, "tftpd: tftp error reading %s", name); 690 goto error; 691 } 692 693 n -= Hdrsize; 694 inblock = buf[2]<<8|buf[3]; 695 if(op == Tftp_DATA) { 696 if(inblock == block) { 697 ret = write(file, buf+Hdrsize, n); 698 if(ret != n) { 699 errstr(errbuf, sizeof errbuf); 700 nak(net, 0, errbuf); 701 syslog(dbg, flog, 702 "tftpd: error writing %s: %s", 703 name, errbuf); 704 goto error; 705 } 706 ack(net, block); 707 block++; 708 } else 709 ack(net, 0xffff); /* tell him to resend */ 710 } 711 } 712 error: 713 close(file); 714 } 715 716 void 717 ack(int fd, ushort block) 718 { 719 uchar ack[4]; 720 int n; 721 722 ack[0] = 0; 723 ack[1] = Tftp_ACK; 724 ack[2] = block>>8; 725 ack[3] = block; 726 727 n = write(fd, ack, 4); 728 if(n < 4) 729 sysfatal("network write: %r"); 730 } 731 732 void 733 nak(int fd, int code, char *msg) 734 { 735 char buf[128]; 736 int n; 737 738 buf[0] = 0; 739 buf[1] = Tftp_ERROR; 740 buf[2] = 0; 741 buf[3] = code; 742 strcpy(buf+4, msg); 743 n = strlen(msg) + 4 + 1; 744 if(write(fd, buf, n) < n) 745 sysfatal("write nak: %r"); 746 } 747 748 void 749 setuser(void) 750 { 751 int fd; 752 753 fd = open("#c/user", OWRITE); 754 if(fd < 0 || write(fd, "none", strlen("none")) < 0) 755 sysfatal("can't become none: %r"); 756 close(fd); 757 if(newns("none", nil) < 0) 758 sysfatal("can't build namespace: %r"); 759 } 760 761 char* 762 lookup(char *sattr, char *sval, char *tattr, char *tval, int len) 763 { 764 static Ndb *db; 765 char *attrs[1]; 766 Ndbtuple *t; 767 768 if(db == nil) 769 db = ndbopen(0); 770 if(db == nil) 771 return nil; 772 773 if(sattr == nil) 774 sattr = ipattr(sval); 775 776 attrs[0] = tattr; 777 t = ndbipinfo(db, sattr, sval, attrs, 1); 778 if(t == nil) 779 return nil; 780 strncpy(tval, t->val, len); 781 tval[len-1] = 0; 782 ndbfree(t); 783 return tval; 784 } 785 786 /* 787 * for sun kernel boots, replace the requested file name with 788 * a one from our database. If the database doesn't specify a file, 789 * don't answer. 790 */ 791 char* 792 sunkernel(char *name) 793 { 794 ulong addr; 795 uchar v4[IPv4addrlen]; 796 uchar v6[IPaddrlen]; 797 char buf[256]; 798 char ipbuf[128]; 799 char *suffix; 800 801 addr = strtoul(name, &suffix, 16); 802 if(suffix-name != 8 || (strcmp(suffix, "") != 0 && strcmp(suffix, ".SUN") != 0)) 803 return name; 804 805 v4[0] = addr>>24; 806 v4[1] = addr>>16; 807 v4[2] = addr>>8; 808 v4[3] = addr; 809 v4tov6(v6, v4); 810 sprint(ipbuf, "%I", v6); 811 return lookup("ip", ipbuf, "bootf", buf, sizeof buf); 812 } 813 814 void 815 remoteaddr(char *dir, char *raddr, int len) 816 { 817 char buf[64]; 818 int fd, n; 819 820 snprint(buf, sizeof(buf), "%s/remote", dir); 821 fd = open(buf, OREAD); 822 if(fd < 0){ 823 snprint(raddr, sizeof(raddr), "unknown"); 824 return; 825 } 826 n = read(fd, raddr, len-1); 827 close(fd); 828 if(n <= 0){ 829 snprint(raddr, sizeof(raddr), "unknown"); 830 return; 831 } 832 if(n > 0) 833 n--; 834 raddr[n] = 0; 835 } 836