skeylogin.c revision 1.41
1/* OpenBSD S/Key (skeylogin.c) 2 * 3 * Authors: 4 * Neil M. Haller <nmh@thumper.bellcore.com> 5 * Philip R. Karn <karn@chicago.qualcomm.com> 6 * John S. Walden <jsw@thumper.bellcore.com> 7 * Scott Chasin <chasin@crimelab.com> 8 * Todd C. Miller <Todd.Miller@courtesan.com> 9 * Angelos D. Keromytis <adk@adk.gr> 10 * 11 * S/Key verification check, lookups, and authentication. 12 * 13 * $OpenBSD: skeylogin.c,v 1.41 2002/02/16 21:27:28 millert Exp $ 14 */ 15 16#include <sys/param.h> 17#ifdef QUOTA 18#include <sys/quota.h> 19#endif 20#include <sys/stat.h> 21#include <sys/time.h> 22#include <sys/resource.h> 23#include <sys/types.h> 24 25#include <ctype.h> 26#include <err.h> 27#include <errno.h> 28#include <fcntl.h> 29#include <paths.h> 30#include <stdio.h> 31#include <stdlib.h> 32#include <string.h> 33#include <time.h> 34#include <unistd.h> 35#include <sha1.h> 36 37#include "skey.h" 38 39static void skey_fakeprompt(char *, char *); 40static char *tgetline(int, char *, size_t, int); 41 42/* 43 * Return an skey challenge string for user 'name'. If successful, 44 * fill in the caller's skey structure and return(0). If unsuccessful 45 * (e.g., if name is unknown) return(-1). 46 * 47 * The file read/write pointer is left at the start of the 48 * record. 49 */ 50int 51skeychallenge(mp, name, ss) 52 struct skey *mp; 53 char *name; 54 char *ss; 55{ 56 int rval; 57 58 rval = skeylookup(mp, name); 59 switch (rval) { 60 case 0: /* Lookup succeeded, return challenge */ 61 (void)sprintf(ss, "otp-%.*s %d %.*s", SKEY_MAX_HASHNAME_LEN, 62 skey_get_algorithm(), mp->n - 1, 63 SKEY_MAX_SEED_LEN, mp->seed); 64 return(0); 65 66 case 1: /* User not found */ 67 (void)fclose(mp->keyfile); 68 mp->keyfile = NULL; 69 /* FALLTHROUGH */ 70 71 default: /* File error */ 72 skey_fakeprompt(name, ss); 73 return(-1); 74 } 75} 76 77/* 78 * Find an entry in the One-time Password database and lock it. 79 * 80 * Return codes: 81 * -1: error in opening database or unable to lock entry 82 * 0: entry found, file R/W pointer positioned at beginning of record 83 * 1: entry not found, file R/W pointer positioned at EOF 84 */ 85int 86skeylookup(mp, name) 87 struct skey *mp; 88 char *name; 89{ 90 FILE *keyfile; 91 int rval; 92 int locked = 0; 93 long recstart = 0; 94 char *cp, *ht = NULL; 95 struct stat statbuf; 96 struct flock fl; 97 98 /* Open _PATH_SKEYKEYS if it exists, else return an error */ 99 if (stat(_PATH_SKEYKEYS, &statbuf) == 0 && 100 (keyfile = mp->keyfile = fopen(_PATH_SKEYKEYS, "r+")) != NULL) { 101 if ((statbuf.st_mode & 0007777) != 0600) 102 fchmod(fileno(keyfile), 0600); 103 } else { 104 mp->keyfile = NULL; 105 return(-1); 106 } 107 108 /* Look up user name in database */ 109 while (!feof(keyfile)) { 110 mp->recstart = recstart = ftell(keyfile); 111 if (fgets(mp->buf, sizeof(mp->buf), keyfile) == NULL) 112 break; 113 if (mp->buf[0] == '#') 114 continue; /* Comment */ 115 mp->len = strlen(mp->buf); 116 cp = mp->buf + mp->len - 1; 117 while (cp >= mp->buf && (*cp == '\n' || *cp == '\r')) 118 *cp-- = '\0'; 119 if ((mp->logname = strtok(mp->buf, " \t")) == NULL || 120 strcmp(mp->logname, name) != 0) 121 continue; 122 if ((cp = strtok(NULL, " \t")) == NULL) 123 continue; 124 /* Save hash type if specified, else use md4 */ 125 if (isalpha(*cp)) { 126 ht = cp; 127 if ((cp = strtok(NULL, " \t")) == NULL) 128 continue; 129 } else { 130 ht = "md4"; 131 } 132 mp->n = atoi(cp); 133 if ((mp->seed = strtok(NULL, " \t")) == NULL) 134 continue; 135 if ((mp->val = strtok(NULL, " \t")) == NULL) 136 continue; 137 138 /* Set hash type */ 139 if (ht && skey_set_algorithm(ht) == NULL) { 140 warnx("Unknown hash algorithm %s, using %s", ht, 141 skey_get_algorithm()); 142 } 143 (void)fseek(keyfile, recstart, SEEK_SET); 144 145 /* If we already aquired the lock we are done */ 146 if (locked) 147 return(0); 148 149 /* Ortherwise, we must lock the record */ 150 fl.l_start = mp->recstart; 151 fl.l_len = mp->len; 152 fl.l_pid = getpid(); 153 fl.l_type = F_WRLCK; 154 fl.l_whence = SEEK_SET; 155 156 /* If we get the lock on the first try we are done */ 157 rval = fcntl(fileno(mp->keyfile), F_SETLK, &fl); 158 if (rval == 0) 159 return(0); 160 else if (errno != EAGAIN) 161 break; 162 163 /* 164 * Wait until we our lock is granted... 165 * Since we didn't get the lock on the first try, someone 166 * else may have modified the record. We need to make 167 * sure the entry hasn't changed name (it could have been 168 * commented out) and re-read it. 169 */ 170 if (fcntl(fileno(mp->keyfile), F_SETLKW, &fl) == -1) 171 break; 172 173 rval = fread(mp->logname, fl.l_len, 1, mp->keyfile); 174 if (rval != fl.l_len || 175 memcmp(mp->logname, name, rval) != 0) { 176 /* username no longer matches so unlock */ 177 fl.l_type = F_UNLCK; 178 fcntl(fileno(mp->keyfile), F_SETLK, &fl); 179 } else { 180 locked = 1; 181 } 182 (void)fseek(keyfile, recstart, SEEK_SET); 183 } 184 185 /* No entry found, fill in what we can... */ 186 memset(mp, 0, sizeof(*mp)); 187 strlcpy(mp->buf, name, sizeof(mp->buf)); 188 mp->logname = mp->buf; 189 mp->len = strlen(mp->buf); 190 mp->keyfile = keyfile; 191 mp->recstart = ftell(keyfile); 192 return(1); 193} 194 195/* 196 * Get the next entry in the One-time Password database. 197 * 198 * Return codes: 199 * -1: error in opening database 200 * 0: next entry found and stored in mp 201 * 1: no more entries, file R/W pointer positioned at EOF 202 */ 203int 204skeygetnext(mp) 205 struct skey *mp; 206{ 207 int rval; 208 int locked = 0; 209 char *cp; 210 struct stat statbuf; 211 struct flock fl; 212 213 /* Open _PATH_SKEYKEYS if it exists, else return an error */ 214 if (mp->keyfile == NULL) { 215 if (stat(_PATH_SKEYKEYS, &statbuf) == 0 && 216 (mp->keyfile = fopen(_PATH_SKEYKEYS, "r+")) != NULL) { 217 if ((statbuf.st_mode & 0007777) != 0600) 218 fchmod(fileno(mp->keyfile), 0600); 219 } else { 220 return(-1); 221 } 222 } else { 223 /* Unlock existing record */ 224 fl.l_start = mp->recstart; 225 fl.l_len = mp->len; 226 fl.l_pid = getpid(); 227 fl.l_type = F_UNLCK; 228 fl.l_whence = SEEK_SET; 229 230 fcntl(fileno(mp->keyfile), F_SETLK, &fl); 231 } 232 233 /* Look up next user in database */ 234 while (!feof(mp->keyfile)) { 235 mp->recstart = ftell(mp->keyfile); 236 if (fgets(mp->buf, sizeof(mp->buf), mp->keyfile) != mp->buf) 237 break; 238 if (mp->buf[0] == '#') 239 continue; /* Comment */ 240 mp->len = strlen(mp->buf); 241 cp = mp->buf + mp->len - 1; 242 while (cp >= mp->buf && (*cp == '\n' || *cp == '\r')) 243 *cp-- = '\0'; 244 if ((mp->logname = strtok(mp->buf, " \t")) == NULL) 245 continue; 246 if ((cp = strtok(NULL, " \t")) == NULL) 247 continue; 248 /* Save hash type if specified, else use md4 */ 249 if (isalpha(*cp)) { 250 if ((cp = strtok(NULL, " \t")) == NULL) 251 continue; 252 } 253 mp->n = atoi(cp); 254 if ((mp->seed = strtok(NULL, " \t")) == NULL) 255 continue; 256 if ((mp->val = strtok(NULL, " \t")) == NULL) 257 continue; 258 259 /* If we already locked the record, we are done */ 260 if (locked) 261 break; 262 263 /* Got a real entry, lock it */ 264 fl.l_start = mp->recstart; 265 fl.l_len = mp->len; 266 fl.l_pid = getpid(); 267 fl.l_type = F_WRLCK; 268 fl.l_whence = SEEK_SET; 269 270 rval = fcntl(fileno(mp->keyfile), F_SETLK, &fl); 271 if (rval == 0) 272 break; 273 else if (errno != EAGAIN) 274 return(-1); 275 276 /* 277 * Someone else has the entry locked, wait 278 * until the lock is free, then re-read the entry. 279 */ 280 rval = fcntl(fileno(mp->keyfile), F_SETLKW, &fl); 281 if (rval == -1) /* Can't get exclusive lock */ 282 return(-1); 283 locked = 1; 284 (void)fseek(mp->keyfile, mp->recstart, SEEK_SET); 285 } 286 return(feof(mp->keyfile)); 287} 288 289/* 290 * Verify response to a S/Key challenge. 291 * 292 * Return codes: 293 * -1: Error of some sort; database unchanged 294 * 0: Verify successful, database updated 295 * 1: Verify failed, database unchanged 296 * 297 * The database file is always closed by this call. 298 */ 299int 300skeyverify(mp, response) 301 struct skey *mp; 302 char *response; 303{ 304 char key[SKEY_BINKEY_SIZE]; 305 char fkey[SKEY_BINKEY_SIZE]; 306 char filekey[SKEY_BINKEY_SIZE]; 307 time_t now; 308 struct tm *tm; 309 struct flock fl; 310 char tbuf[27]; 311 char *cp; 312 int len; 313 314 /* 315 * The record should already be locked but lock it again 316 * just to be safe. We don't wait for the lock to become 317 * available since we should already have it... 318 */ 319 fl.l_start = mp->recstart; 320 fl.l_len = mp->len; 321 fl.l_pid = getpid(); 322 fl.l_type = F_WRLCK; 323 fl.l_whence = SEEK_SET; 324 if (fcntl(fileno(mp->keyfile), F_SETLK, &fl) != 0) { 325 (void)fclose(mp->keyfile); 326 mp->keyfile = NULL; 327 return(-1); 328 } 329 330 time(&now); 331 tm = localtime(&now); 332 (void)strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm); 333 334 if (response == NULL) { 335 (void)fclose(mp->keyfile); 336 mp->keyfile = NULL; 337 return(-1); 338 } 339 rip(response); 340 341 /* Convert response to binary */ 342 if (etob(key, response) != 1 && atob8(key, response) != 0) { 343 /* Neither english words nor ascii hex */ 344 (void)fclose(mp->keyfile); 345 mp->keyfile = NULL; 346 return(-1); 347 } 348 349 /* Compute fkey = f(key) */ 350 (void)memcpy(fkey, key, sizeof(key)); 351 (void)fflush(stdout); 352 f(fkey); 353 354 /* Reread the file record NOW */ 355 (void)fseek(mp->keyfile, mp->recstart, SEEK_SET); 356 if (fgets(mp->buf, sizeof(mp->buf), mp->keyfile) == NULL) { 357 (void)fclose(mp->keyfile); 358 mp->keyfile = NULL; 359 return(-1); 360 } 361 len = strlen(mp->buf) - 1; 362 cp = mp->buf + len; 363 while (cp >= mp->buf && (*cp == '\n' || *cp == '\r')) 364 *cp-- = '\0'; 365 mp->logname = strtok(mp->buf, " \t"); 366 cp = strtok(NULL, " \t") ; 367 if (isalpha(*cp)) 368 cp = strtok(NULL, " \t") ; 369 mp->seed = strtok(NULL, " \t"); 370 mp->val = strtok(NULL, " \t"); 371 /* And convert file value to hex for comparison */ 372 atob8(filekey, mp->val); 373 374 /* Do actual comparison */ 375 if (memcmp(filekey, fkey, SKEY_BINKEY_SIZE) != 0){ 376 /* Wrong response */ 377 (void)fclose(mp->keyfile); 378 mp->keyfile = NULL; 379 return(1); 380 } 381 382 /* 383 * Update key in database by overwriting entire record. Note 384 * that we must write exactly the same number of bytes as in 385 * the original record (note fixed width field for N) 386 */ 387 btoa8(mp->val,key); 388 mp->n--; 389 (void)fseek(mp->keyfile, mp->recstart, SEEK_SET); 390 len -= strlen(mp->logname) + strlen(skey_get_algorithm()) + 391 strlen(mp->val) + strlen(tbuf) + 9; 392 /* 393 * If we run out of room it is because we read an old-style 394 * md4 entry without an explicit hash type. 395 */ 396 if (len < strlen(mp->seed)) 397 (void)fprintf(mp->keyfile, "%s %04d %-16s %s %-21s\n", 398 mp->logname, mp->n, mp->seed, mp->val, tbuf); 399 else 400 (void)fprintf(mp->keyfile, "%s %s %04d %-*s %s %-21s\n", 401 mp->logname, skey_get_algorithm(), mp->n, 402 len, mp->seed, mp->val, tbuf); 403 404 (void)fclose(mp->keyfile); 405 mp->keyfile = NULL; 406 return(0); 407} 408 409/* 410 * skey_haskey() 411 * 412 * Returns: 1 user doesn't exist, -1 file error, 0 user exists. 413 * 414 */ 415int 416skey_haskey(username) 417 char *username; 418{ 419 struct skey skey; 420 int i; 421 422 i = skeylookup(&skey, username); 423 if (skey.keyfile != NULL) { 424 fclose(skey.keyfile); 425 skey.keyfile = NULL; 426 } 427 return(i); 428} 429 430/* 431 * skey_keyinfo() 432 * 433 * Returns the current sequence number and 434 * seed for the passed user. 435 * 436 */ 437char * 438skey_keyinfo(username) 439 char *username; 440{ 441 int i; 442 static char str[SKEY_MAX_CHALLENGE]; 443 struct skey skey; 444 445 i = skeychallenge(&skey, username, str); 446 if (i == -1) 447 return(0); 448 449 if (skey.keyfile != NULL) { 450 fclose(skey.keyfile); 451 skey.keyfile = NULL; 452 } 453 return(str); 454} 455 456/* 457 * skey_passcheck() 458 * 459 * Check to see if answer is the correct one to the current 460 * challenge. 461 * 462 * Returns: 0 success, -1 failure 463 * 464 */ 465int 466skey_passcheck(username, passwd) 467 char *username; 468 char *passwd; 469{ 470 int i; 471 struct skey skey; 472 473 i = skeylookup(&skey, username); 474 if (i == -1 || i == 1) 475 return(-1); 476 477 if (skeyverify(&skey, passwd) == 0) 478 return(skey.n); 479 480 return(-1); 481} 482 483#define ROUND(x) (((x)[0] << 24) + (((x)[1]) << 16) + (((x)[2]) << 8) + \ 484 ((x)[3])) 485 486/* 487 * hash_collapse() 488 */ 489static u_int32_t 490hash_collapse(s) 491 u_char *s; 492{ 493 int len, target; 494 u_int32_t i; 495 496 if ((strlen(s) % sizeof(u_int32_t)) == 0) 497 target = strlen(s); /* Multiple of 4 */ 498 else 499 target = strlen(s) - (strlen(s) % sizeof(u_int32_t)); 500 501 for (i = 0, len = 0; len < target; len += 4) 502 i ^= ROUND(s + len); 503 504 return i; 505} 506 507/* 508 * skey_fakeprompt() 509 * 510 * Generate a fake prompt for the specified user. 511 * 512 */ 513static void 514skey_fakeprompt(username, skeyprompt) 515 char *username; 516 char *skeyprompt; 517{ 518 int i; 519 u_int ptr; 520 u_char hseed[SKEY_MAX_SEED_LEN], flg = 1, *up; 521 char *secret, pbuf[SKEY_MAX_PW_LEN+1]; 522 char *p, *u; 523 size_t secretlen; 524 SHA1_CTX ctx; 525 526 /* 527 * Base first 4 chars of seed on hostname. 528 * Add some filler for short hostnames if necessary. 529 */ 530 if (gethostname(pbuf, sizeof(pbuf)) == -1) 531 *(p = pbuf) = '.'; 532 else 533 for (p = pbuf; *p && isalnum(*p); p++) 534 if (isalpha(*p) && isupper(*p)) 535 *p = tolower(*p); 536 if (*p && pbuf - p < 4) 537 (void)strncpy(p, "asjd", 4 - (pbuf - p)); 538 pbuf[4] = '\0'; 539 540 /* Hash the username if possible */ 541 if ((up = SHA1Data(username, strlen(username), NULL)) != NULL) { 542 struct stat sb; 543 time_t t; 544 int fd; 545 546 /* Collapse the hash */ 547 ptr = hash_collapse(up); 548 memset(up, 0, strlen(up)); 549 550 /* See if the random file's there, else use ctime */ 551 if ((fd = open(_SKEY_RAND_FILE_PATH_, O_RDONLY)) != -1 552 && fstat(fd, &sb) == 0 && 553 sb.st_size > (off_t)SKEY_MAX_SEED_LEN && 554 lseek(fd, ptr % (sb.st_size - SKEY_MAX_SEED_LEN), 555 SEEK_SET) != -1 && read(fd, hseed, 556 SKEY_MAX_SEED_LEN) == SKEY_MAX_SEED_LEN) { 557 close(fd); 558 fd = -1; 559 secret = hseed; 560 secretlen = SKEY_MAX_SEED_LEN; 561 flg = 0; 562 } else if (!stat(_PATH_MEM, &sb) || !stat("/", &sb)) { 563 t = sb.st_ctime; 564 secret = ctime(&t); 565 secretlen = strlen(secret); 566 flg = 0; 567 } 568 if (fd != -1) 569 close(fd); 570 } 571 572 /* Put that in your pipe and smoke it */ 573 if (flg == 0) { 574 /* Hash secret value with username */ 575 SHA1Init(&ctx); 576 SHA1Update(&ctx, secret, secretlen); 577 SHA1Update(&ctx, username, strlen(username)); 578 SHA1End(&ctx, up); 579 580 /* Zero out */ 581 memset(secret, 0, secretlen); 582 583 /* Now hash the hash */ 584 SHA1Init(&ctx); 585 SHA1Update(&ctx, up, strlen(up)); 586 SHA1End(&ctx, up); 587 588 ptr = hash_collapse(up + 4); 589 590 for (i = 4; i < 9; i++) { 591 pbuf[i] = (ptr % 10) + '0'; 592 ptr /= 10; 593 } 594 pbuf[i] = '\0'; 595 596 /* Sequence number */ 597 ptr = ((up[2] + up[3]) % 99) + 1; 598 599 memset(up, 0, 20); /* SHA1 specific */ 600 free(up); 601 602 (void)sprintf(skeyprompt, 603 "otp-%.*s %d %.*s", 604 SKEY_MAX_HASHNAME_LEN, 605 skey_get_algorithm(), 606 ptr, SKEY_MAX_SEED_LEN, 607 pbuf); 608 } else { 609 /* Base last 8 chars of seed on username */ 610 u = username; 611 i = 8; 612 p = &pbuf[4]; 613 do { 614 if (*u == 0) { 615 /* Pad remainder with zeros */ 616 while (--i >= 0) 617 *p++ = '0'; 618 break; 619 } 620 621 *p++ = (*u++ % 10) + '0'; 622 } while (--i != 0); 623 pbuf[12] = '\0'; 624 625 (void)sprintf(skeyprompt, "otp-%.*s %d %.*s", 626 SKEY_MAX_HASHNAME_LEN, 627 skey_get_algorithm(), 628 99, SKEY_MAX_SEED_LEN, pbuf); 629 } 630} 631 632/* 633 * skey_authenticate() 634 * 635 * Used when calling program will allow input of the user's 636 * response to the challenge. 637 * 638 * Returns: 0 success, -1 failure 639 * 640 */ 641int 642skey_authenticate(username) 643 char *username; 644{ 645 int i; 646 char pbuf[SKEY_MAX_PW_LEN+1], skeyprompt[SKEY_MAX_CHALLENGE+1]; 647 struct skey skey; 648 649 /* Get the S/Key challenge (may be fake) */ 650 i = skeychallenge(&skey, username, skeyprompt); 651 (void)fprintf(stderr, "%s\nResponse: ", skeyprompt); 652 (void)fflush(stderr); 653 654 /* Time out on user input after 2 minutes */ 655 tgetline(fileno(stdin), pbuf, sizeof(pbuf), 120); 656 sevenbit(pbuf); 657 (void)rewind(stdin); 658 659 /* Is it a valid response? */ 660 if (i == 0 && skeyverify(&skey, pbuf) == 0) { 661 if (skey.n < 5) { 662 (void)fprintf(stderr, 663 "\nWarning! Key initialization needed soon. (%d logins left)\n", 664 skey.n); 665 } 666 return(0); 667 } 668 return(-1); 669} 670 671/* 672 * Comment out user's entry in the S/Key database 673 * 674 * Return codes: 675 * -1: Write error; database unchanged 676 * 0: Database updated 677 * 678 * The database file is always closed by this call. 679 */ 680int 681skeyzero(mp) 682 struct skey *mp; 683{ 684 685 /* 686 * Seek to the right place and write comment character 687 * which effectively zero's out the entry. 688 */ 689 (void)fseek(mp->keyfile, mp->recstart, SEEK_SET); 690 if (fputc('#', mp->keyfile) == EOF) { 691 fclose(mp->keyfile); 692 mp->keyfile = NULL; 693 return(-1); 694 } 695 696 (void)fclose(mp->keyfile); 697 mp->keyfile = NULL; 698 return(0); 699} 700 701/* 702 * Unlock current entry in the One-time Password database. 703 * 704 * Return codes: 705 * -1: unable to lock the record 706 * 0: record was successfully unlocked 707 */ 708int 709skey_unlock(mp) 710 struct skey *mp; 711{ 712 struct flock fl; 713 714 if (mp->logname == NULL || mp->keyfile == NULL) 715 return(-1); 716 717 fl.l_start = mp->recstart; 718 fl.l_len = mp->len; 719 fl.l_pid = getpid(); 720 fl.l_type = F_UNLCK; 721 fl.l_whence = SEEK_SET; 722 723 return(fcntl(fileno(mp->keyfile), F_SETLK, &fl)); 724} 725 726/* 727 * Get a line of input (optionally timing out) and place it in buf. 728 */ 729static char * 730tgetline(fd, buf, bufsiz, timeout) 731 int fd; 732 char *buf; 733 size_t bufsiz; 734 int timeout; 735{ 736 size_t left; 737 int n; 738 fd_set *readfds = NULL; 739 struct timeval tv; 740 char c; 741 char *cp; 742 743 if (bufsiz == 0) 744 return(NULL); /* sanity */ 745 746 cp = buf; 747 left = bufsiz; 748 749 /* 750 * Timeout of <= 0 means no timeout. 751 */ 752 if (timeout > 0) { 753 /* Setup for select(2) */ 754 n = howmany(fd + 1, NFDBITS) * sizeof(fd_mask); 755 if ((readfds = (fd_set *) malloc(n)) == NULL) 756 return(NULL); 757 (void) memset(readfds, 0, n); 758 759 /* Set timeout for select */ 760 tv.tv_sec = timeout; 761 tv.tv_usec = 0; 762 763 while (--left) { 764 FD_SET(fd, readfds); 765 766 /* Make sure there is something to read (or timeout) */ 767 while ((n = select(fd + 1, readfds, 0, 0, &tv)) == -1 && 768 (errno == EINTR || errno == EAGAIN)) 769 ; 770 if (n == 0) { 771 free(readfds); 772 return(NULL); /* timeout */ 773 } 774 775 /* Read a character, exit loop on error, EOF or EOL */ 776 n = read(fd, &c, 1); 777 if (n != 1 || c == '\n' || c == '\r') 778 break; 779 *cp++ = c; 780 } 781 free(readfds); 782 } else { 783 /* Keep reading until out of space, EOF, error, or newline */ 784 while (--left && (n = read(fd, &c, 1)) == 1 && c != '\n' && c != '\r') 785 *cp++ = c; 786 } 787 *cp = '\0'; 788 789 return(cp == buf ? NULL : buf); 790} 791