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 (bp + 3 >= ep) 344 return -1; 345 *bp++ = '\0'; 346 *bp++ = '\0'; /* overkill */ 347 *bp++ = '\0'; 348 349 if (write(fd, buf, bp - buf) < bp - buf) { 350 syslog(dbg, flog, "tftpd network write error on oack to %s: %r", 351 raddr); 352 sysfatal("tftpd: network write error: %r"); 353 } 354 if(Debug) 355 syslog(dbg, flog, "tftpd oack: options to %s", raddr); 356 return nopts; 357 } 358 359 static void 360 optlog(char *bytes, char *p, int dlen) 361 { 362 char *bp; 363 364 bp = bytes; 365 sprint(bp, "tftpd %d option bytes: ", dlen); 366 bp += strlen(bp); 367 for (; dlen > 0; dlen--, p++) 368 *bp++ = *p? *p: ' '; 369 *bp = '\0'; 370 syslog(dbg, flog, "%s", bytes); 371 } 372 373 /* 374 * replace one occurrence of %[ICE] with ip, cfgpxe name, or ether mac, resp. 375 * we can't easily use $ because u-boot has stranger quoting rules than sh. 376 */ 377 char * 378 mapname(char *file) 379 { 380 int nf; 381 char *p, *newnm, *cur, *arpf, *ln, *remip, *bang; 382 char *fields[4]; 383 Biobuf *arp; 384 385 p = strchr(file, '%'); 386 if (p == nil || p[1] == '\0') 387 return strdup(file); 388 389 remip = strdup(raddr); 390 newnm = mallocz(strlen(file) + Maxpath, 1); 391 if (remip == nil || newnm == nil) 392 sysfatal("out of memory"); 393 394 bang = strchr(remip, '!'); 395 if (bang) 396 *bang = '\0'; /* remove !port */ 397 398 memmove(newnm, file, p - file); /* copy up to % */ 399 cur = newnm + strlen(newnm); 400 switch(p[1]) { 401 case 'I': 402 strcpy(cur, remip); /* remote's IP */ 403 break; 404 case 'C': 405 strcpy(cur, "/cfg/pxe/"); 406 cur += strlen(cur); 407 /* fall through */ 408 case 'E': 409 /* look up remote's IP in /net/arp to get mac. */ 410 arpf = smprint("%s/arp", net); 411 arp = Bopen(arpf, OREAD); 412 free(arpf); 413 if (arp == nil) 414 break; 415 /* read lines looking for remip in 3rd field of 4 */ 416 while ((ln = Brdline(arp, '\n')) != nil) { 417 ln[Blinelen(arp)-1] = 0; 418 nf = tokenize(ln, fields, nelem(fields)); 419 if (nf >= 4 && strcmp(fields[2], remip) == 0) { 420 strcpy(cur, fields[3]); 421 break; 422 } 423 } 424 Bterm(arp); 425 break; 426 } 427 strcat(newnm, p + 2); /* tail following %x */ 428 free(remip); 429 return newnm; 430 } 431 432 void 433 doserve(int fd) 434 { 435 int dlen, opts; 436 char *mode, *p, *file; 437 short op; 438 439 dlen = read(fd, bigbuf, sizeof(bigbuf)-1); 440 if(dlen < 0) 441 sysfatal("listen read: %r"); 442 443 bigbuf[dlen] = '\0'; 444 op = (bigbuf[0]<<8) | bigbuf[1]; 445 dlen -= Opsize; 446 mode = file = bigbuf + Opsize; 447 while(*mode != '\0' && dlen--) 448 mode++; 449 mode++; 450 p = mode; 451 while(*p && dlen--) 452 p++; 453 454 file = mapname(file); /* we don't free the result; minor leak */ 455 456 if(dlen == 0) { 457 nak(fd, 0, "bad tftpmode"); 458 close(fd); 459 syslog(dbg, flog, "tftpd %d bad mode %s for file %s from %s", 460 pid, mode, file, raddr); 461 return; 462 } 463 464 if(op != Tftp_READ && op != Tftp_WRITE) { 465 nak(fd, Errbadop, "Illegal TFTP operation"); 466 close(fd); 467 syslog(dbg, flog, "tftpd %d bad request %d (%s) %s", pid, op, 468 (op < nelem(opnames)? opnames[op]: "gok"), raddr); 469 return; 470 } 471 472 if(restricted){ 473 if(file[0] == '#' || strncmp(file, "../", 3) == 0 || 474 strstr(file, "/../") != nil || 475 (file[0] == '/' && strncmp(file, dirsl, dirsllen) != 0)){ 476 nak(fd, Errnoaccess, "Permission denied"); 477 close(fd); 478 syslog(dbg, flog, "tftpd %d bad request %d from %s file %s", 479 pid, op, raddr, file); 480 return; 481 } 482 } 483 484 /* 485 * options are supposed to be negotiated, but the cavium board's 486 * u-boot really wants us to use a block size of 1432 bytes and won't 487 * take `no' for an answer. 488 */ 489 p++; /* skip NUL after mode */ 490 dlen--; 491 opts = 0; 492 if(dlen > 0) { /* might have options */ 493 char bytes[32*1024]; 494 495 if(Debug) 496 optlog(bytes, p, dlen); 497 opts = options(fd, bytes, sizeof bytes, file, op, p, dlen); 498 if (opts < 0) 499 return; 500 } 501 if(op == Tftp_READ) 502 sendfile(fd, file, mode, opts); 503 else 504 recvfile(fd, file, mode); 505 } 506 507 void 508 catcher(void *junk, char *msg) 509 { 510 USED(junk); 511 512 if(strncmp(msg, "exit", 4) == 0) 513 noted(NDFLT); 514 noted(NCONT); 515 } 516 517 static int 518 awaitack(int fd, int block) 519 { 520 int ackblock, al, rxl; 521 ushort op; 522 uchar ack[1024]; 523 524 for(rxl = 0; rxl < 10; rxl++) { 525 memset(ack, 0, Hdrsize); 526 alarm(1000); 527 al = read(fd, ack, sizeof(ack)); 528 alarm(0); 529 if(al < 0) { 530 if (Debug) 531 syslog(dbg, flog, "tftpd %d timed out " 532 "waiting for ack from %s", pid, raddr); 533 return Ackrexmit; 534 } 535 op = ack[0]<<8|ack[1]; 536 if(op == Tftp_ERROR) { 537 if (Debug) 538 syslog(dbg, flog, "tftpd %d got error " 539 "waiting for ack from %s", pid, raddr); 540 return Ackerr; 541 } else if(op != Tftp_ACK) { 542 syslog(dbg, flog, "tftpd %d rcvd %s op from %s", pid, 543 (op < nelem(opnames)? opnames[op]: "gok"), 544 raddr); 545 return Ackerr; 546 } 547 ackblock = ack[2]<<8|ack[3]; 548 if (Debug) 549 syslog(dbg, flog, "tftpd %d read ack of %d bytes " 550 "for block %d", pid, al, ackblock); 551 if(ackblock == block) 552 return Ackok; /* for block just sent */ 553 else if(ackblock == block + 1) /* intel pxe eof bug */ 554 return Ackok; 555 else if(ackblock == 0xffff) 556 return Ackrexmit; 557 else 558 /* ack is for some other block; ignore it, try again */ 559 syslog(dbg, flog, "tftpd %d expected ack for block %d, " 560 "got %d", pid, block, ackblock); 561 } 562 return Ackrexmit; 563 } 564 565 void 566 sendfile(int fd, char *name, char *mode, int opts) 567 { 568 int file, block, ret, rexmit, n, txtry; 569 uchar buf[Maxsegsize+Hdrsize]; 570 char errbuf[Maxerr]; 571 572 file = -1; 573 syslog(dbg, flog, "tftpd %d send file '%s' %s to %s", 574 pid, name, mode, raddr); 575 name = sunkernel(name); 576 if(name == 0){ 577 nak(fd, 0, "not in our database"); 578 goto error; 579 } 580 581 notify(catcher); 582 583 file = open(name, OREAD); 584 if(file < 0) { 585 errstr(errbuf, sizeof errbuf); 586 nak(fd, 0, errbuf); 587 goto error; 588 } 589 block = 0; 590 rexmit = Ackok; 591 n = 0; 592 /* 593 * if we sent an oack previously, wait for the client's ack or error. 594 * if we get no ack for our oack, it could be that we returned 595 * a tsize that the client can't handle, or it could be intel 596 * pxe just read-with-tsize to get size, couldn't be bothered to 597 * ack our oack and has just gone ahead and issued another read. 598 */ 599 if(opts && awaitack(fd, 0) != Ackok) 600 goto error; 601 602 for(txtry = 0; txtry < timeout;) { 603 if(rexmit == Ackok) { 604 block++; 605 buf[0] = 0; 606 buf[1] = Tftp_DATA; 607 buf[2] = block>>8; 608 buf[3] = block; 609 n = read(file, buf+Hdrsize, blksize); 610 if(n < 0) { 611 errstr(errbuf, sizeof errbuf); 612 nak(fd, 0, errbuf); 613 goto error; 614 } 615 txtry = 0; 616 } 617 else { 618 syslog(dbg, flog, "tftpd %d rexmit %d %s:%d to %s", 619 pid, Hdrsize+n, name, block, raddr); 620 txtry++; 621 } 622 623 ret = write(fd, buf, Hdrsize+n); 624 if(ret < Hdrsize+n) { 625 syslog(dbg, flog, 626 "tftpd network write error on %s to %s: %r", 627 name, raddr); 628 sysfatal("tftpd: network write error: %r"); 629 } 630 if (Debug) 631 syslog(dbg, flog, "tftpd %d sent block %d", pid, block); 632 633 rexmit = awaitack(fd, block); 634 if (rexmit == Ackerr) 635 break; 636 if(ret != blksize+Hdrsize && rexmit == Ackok) 637 break; 638 } 639 syslog(dbg, flog, "tftpd %d done sending file '%s' %s to %s", 640 pid, name, mode, raddr); 641 error: 642 close(fd); 643 close(file); 644 } 645 646 void 647 recvfile(int fd, 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(fd, 0, errbuf); 660 syslog(dbg, flog, "can't create %s: %r", name); 661 return; 662 } 663 664 block = 0; 665 ack(fd, block); 666 block++; 667 668 for (;;) { 669 alarm(15000); 670 n = read(fd, 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(fd, 0, errbuf); 701 syslog(dbg, flog, 702 "tftpd: error writing %s: %s", 703 name, errbuf); 704 goto error; 705 } 706 ack(fd, block); 707 block++; 708 } else 709 ack(fd, 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