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