File: | src/lib/libskey/skeylogin.c |
Warning: | line 285, column 7 Although the value stored to 'cp' is used in the enclosing expression, the value is never actually read from 'cp' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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 <millert@openbsd.org> |
9 | * Angelos D. Keromytis <adk@adk.gr> |
10 | * |
11 | * S/Key verification check, lookups, and authentication. |
12 | * |
13 | * $OpenBSD: skeylogin.c,v 1.64 2023/03/15 17:01:35 millert Exp $ |
14 | */ |
15 | |
16 | #ifdef QUOTA |
17 | #include <sys/quota.h> |
18 | #endif |
19 | #include <sys/stat.h> |
20 | #include <sys/time.h> |
21 | #include <sys/resource.h> |
22 | |
23 | #include <ctype.h> |
24 | #include <err.h> |
25 | #include <errno(*__errno()).h> |
26 | #include <fcntl.h> |
27 | #include <paths.h> |
28 | #include <poll.h> |
29 | #include <stdio.h> |
30 | #include <stdlib.h> |
31 | #include <string.h> |
32 | #include <time.h> |
33 | #include <unistd.h> |
34 | #include <limits.h> |
35 | #include <sha1.h> |
36 | |
37 | #include "skey.h" |
38 | |
39 | static void skey_fakeprompt(char *, char *); |
40 | static char *tgetline(int, char *, size_t, int); |
41 | static int skeygetent(int, struct skey *, const char *); |
42 | |
43 | /* |
44 | * Return an skey challenge string for user 'name'. If successful, |
45 | * fill in the caller's skey structure and return (0). If unsuccessful |
46 | * (e.g., if name is unknown) return (-1). |
47 | * |
48 | * The file read/write pointer is left at the start of the record. |
49 | */ |
50 | int |
51 | skeychallenge2(int fd, struct skey *mp, char *name, char *ss) |
52 | { |
53 | int rval; |
54 | |
55 | memset(mp, 0, sizeof(*mp)); |
56 | rval = skeygetent(fd, mp, name); |
57 | |
58 | switch (rval) { |
59 | case 0: /* Lookup succeeded, return challenge */ |
60 | (void)snprintf(ss, SKEY_MAX_CHALLENGE(11 + 6 + 16), |
61 | "otp-%.*s %d %.*s", SKEY_MAX_HASHNAME_LEN6, |
62 | skey_get_algorithm(), mp->n - 1, |
63 | SKEY_MAX_SEED_LEN16, mp->seed); |
64 | return (0); |
65 | |
66 | case 1: /* User not found */ |
67 | if (mp->keyfile) { |
68 | (void)fclose(mp->keyfile); |
69 | mp->keyfile = NULL((void *)0); |
70 | } |
71 | /* FALLTHROUGH */ |
72 | |
73 | default: /* File error */ |
74 | skey_fakeprompt(name, ss); |
75 | return (-1); |
76 | } |
77 | } |
78 | |
79 | int |
80 | skeychallenge(struct skey *mp, char *name, char *ss) |
81 | { |
82 | return (skeychallenge2(-1, mp, name, ss)); |
83 | } |
84 | |
85 | /* |
86 | * Get an entry in the One-time Password database and lock it. |
87 | * |
88 | * Return codes: |
89 | * -1: error in opening database or unable to lock entry |
90 | * 0: entry found, file R/W pointer positioned at beginning of record |
91 | * 1: entry not found |
92 | */ |
93 | static int |
94 | skeygetent(int fd, struct skey *mp, const char *name) |
95 | { |
96 | char *cp, filename[PATH_MAX1024], *last; |
97 | struct stat statbuf; |
98 | const char *errstr; |
99 | size_t nread; |
100 | FILE *keyfile; |
101 | |
102 | /* Check to see that /etc/skey has not been disabled. */ |
103 | if (stat(_PATH_SKEYDIR"/etc/skey", &statbuf) != 0) |
104 | return (-1); |
105 | if ((statbuf.st_mode & ALLPERMS(0004000|0002000|0001000|0000700|0000070|0000007)) == 0) { |
106 | errno(*__errno()) = EPERM1; |
107 | return (-1); |
108 | } |
109 | |
110 | if (fd == -1) { |
111 | /* Open the user's database entry, creating it as needed. */ |
112 | if (snprintf(filename, sizeof(filename), "%s/%s", _PATH_SKEYDIR"/etc/skey", |
113 | name) >= sizeof(filename)) { |
114 | errno(*__errno()) = ENAMETOOLONG63; |
115 | return (-1); |
116 | } |
117 | if ((fd = open(filename, O_RDWR0x0002 | O_NOFOLLOW0x0100 | O_NONBLOCK0x0004, |
118 | S_IRUSR0000400 | S_IWUSR0000200)) == -1) { |
119 | if (errno(*__errno()) == ENOENT2) |
120 | goto not_found; |
121 | return (-1); |
122 | } |
123 | } |
124 | |
125 | /* Lock and stat the user's skey file. */ |
126 | if (flock(fd, LOCK_EX0x02) != 0 || fstat(fd, &statbuf) != 0) { |
127 | close(fd); |
128 | return (-1); |
129 | } |
130 | if (statbuf.st_size == 0) |
131 | goto not_found; |
132 | |
133 | /* Sanity checks. */ |
134 | if ((statbuf.st_mode & ALLPERMS(0004000|0002000|0001000|0000700|0000070|0000007)) != (S_IRUSR0000400 | S_IWUSR0000200) || |
135 | !S_ISREG(statbuf.st_mode)((statbuf.st_mode & 0170000) == 0100000) || statbuf.st_nlink != 1 || |
136 | (keyfile = fdopen(fd, "r+")) == NULL((void *)0)) { |
137 | close(fd); |
138 | return (-1); |
139 | } |
140 | |
141 | /* At this point, we are committed. */ |
142 | mp->keyfile = keyfile; |
143 | |
144 | if ((nread = fread(mp->buf, 1, sizeof(mp->buf), keyfile)) == 0 || |
145 | !isspace((unsigned char)mp->buf[nread - 1])) |
146 | goto bad_keyfile; |
147 | mp->buf[nread - 1] = '\0'; |
148 | |
149 | if ((mp->logname = strtok_r(mp->buf, " \t\n\r", &last)) == NULL((void *)0) || |
150 | strcmp(mp->logname, name) != 0) |
151 | goto bad_keyfile; |
152 | if ((cp = strtok_r(NULL((void *)0), " \t\n\r", &last)) == NULL((void *)0)) |
153 | goto bad_keyfile; |
154 | if (skey_set_algorithm(cp) == NULL((void *)0)) |
155 | goto bad_keyfile; |
156 | if ((cp = strtok_r(NULL((void *)0), " \t\n\r", &last)) == NULL((void *)0)) |
157 | goto bad_keyfile; |
158 | mp->n = strtonum(cp, 0, UINT_MAX0xffffffffU, &errstr); |
159 | if (errstr) |
160 | goto bad_keyfile; |
161 | if ((mp->seed = strtok_r(NULL((void *)0), " \t\n\r", &last)) == NULL((void *)0)) |
162 | goto bad_keyfile; |
163 | if ((mp->val = strtok_r(NULL((void *)0), " \t\n\r", &last)) == NULL((void *)0)) |
164 | goto bad_keyfile; |
165 | |
166 | (void)fseek(keyfile, 0L, SEEK_SET0); |
167 | return (0); |
168 | |
169 | bad_keyfile: |
170 | fclose(keyfile); |
171 | return (-1); |
172 | |
173 | not_found: |
174 | /* No existing entry, fill in what we can and return */ |
175 | memset(mp, 0, sizeof(*mp)); |
176 | strlcpy(mp->buf, name, sizeof(mp->buf)); |
177 | mp->logname = mp->buf; |
178 | if (fd != -1) |
179 | close(fd); |
180 | return (1); |
181 | } |
182 | |
183 | /* |
184 | * Look up an entry in the One-time Password database and lock it. |
185 | * Zeroes out the passed in struct skey before using it. |
186 | * |
187 | * Return codes: |
188 | * -1: error in opening database or unable to lock entry |
189 | * 0: entry found, file R/W pointer positioned at beginning of record |
190 | * 1: entry not found |
191 | */ |
192 | int |
193 | skeylookup(struct skey *mp, char *name) |
194 | { |
195 | memset(mp, 0, sizeof(*mp)); |
196 | return (skeygetent(-1, mp, name)); |
197 | } |
198 | |
199 | /* |
200 | * Get the next entry in the One-time Password database. |
201 | * |
202 | * Return codes: |
203 | * -1: error in opening database |
204 | * 0: next entry found and stored in mp |
205 | * 1: no more entries, keydir is closed. |
206 | */ |
207 | int |
208 | skeygetnext(struct skey *mp) |
209 | { |
210 | struct dirent entry, *dp; |
211 | int rval; |
212 | |
213 | if (mp->keyfile != NULL((void *)0)) { |
214 | fclose(mp->keyfile); |
215 | mp->keyfile = NULL((void *)0); |
216 | } |
217 | |
218 | /* Open _PATH_SKEYDIR if it exists, else return an error */ |
219 | if (mp->keydir == NULL((void *)0) && (mp->keydir = opendir(_PATH_SKEYDIR"/etc/skey")) == NULL((void *)0)) |
220 | return (-1); |
221 | |
222 | rval = 1; |
223 | while ((readdir_r(mp->keydir, &entry, &dp)) == 0 && dp == &entry) { |
224 | /* Skip dot files and zero-length files. */ |
225 | if (entry.d_name[0] != '.' && |
226 | (rval = skeygetent(-1, mp, entry.d_name)) != 1) |
227 | break; |
228 | } |
229 | |
230 | if (dp == NULL((void *)0)) { |
231 | closedir(mp->keydir); |
232 | mp->keydir = NULL((void *)0); |
233 | } |
234 | |
235 | return (rval); |
236 | } |
237 | |
238 | /* |
239 | * Verify response to a S/Key challenge. |
240 | * |
241 | * Return codes: |
242 | * -1: Error of some sort; database unchanged |
243 | * 0: Verify successful, database updated |
244 | * 1: Verify failed, database unchanged |
245 | * |
246 | * The database file is always closed by this call. |
247 | */ |
248 | int |
249 | skeyverify(struct skey *mp, char *response) |
250 | { |
251 | char key[SKEY_BINKEY_SIZE8], fkey[SKEY_BINKEY_SIZE8]; |
252 | char filekey[SKEY_BINKEY_SIZE8], *cp, *last; |
253 | size_t nread; |
254 | |
255 | if (response == NULL((void *)0)) |
256 | goto verify_failure; |
257 | |
258 | /* |
259 | * The record should already be locked but lock it again |
260 | * just to be safe. We don't wait for the lock to become |
261 | * available since we should already have it... |
262 | */ |
263 | if (flock(fileno(mp->keyfile)(!__isthreaded ? ((mp->keyfile)->_file) : (fileno)(mp-> keyfile)), LOCK_EX0x02 | LOCK_NB0x04) != 0) |
264 | goto verify_failure; |
265 | |
266 | /* Convert response to binary */ |
267 | rip(response); |
268 | if (etob(key, response) != 1 && atob8(key, response) != 0) |
269 | goto verify_failure; /* Neither english words nor ascii hex */ |
270 | |
271 | /* Compute fkey = f(key) */ |
272 | (void)memcpy(fkey, key, sizeof(key)); |
273 | f(fkey); |
274 | |
275 | /* |
276 | * Reread the file record NOW in case it has been modified. |
277 | * The only field we really need to worry about is mp->val. |
278 | */ |
279 | (void)fseek(mp->keyfile, 0L, SEEK_SET0); |
280 | if ((nread = fread(mp->buf, 1, sizeof(mp->buf), mp->keyfile)) == 0 || |
281 | !isspace((unsigned char)mp->buf[nread - 1])) |
282 | goto verify_failure; |
283 | if ((mp->logname = strtok_r(mp->buf, " \t\r\n", &last)) == NULL((void *)0)) |
284 | goto verify_failure; |
285 | if ((cp = strtok_r(NULL((void *)0), " \t\r\n", &last)) == NULL((void *)0)) |
Although the value stored to 'cp' is used in the enclosing expression, the value is never actually read from 'cp' | |
286 | goto verify_failure; |
287 | if ((cp = strtok_r(NULL((void *)0), " \t\r\n", &last)) == NULL((void *)0)) |
288 | goto verify_failure; |
289 | if ((mp->seed = strtok_r(NULL((void *)0), " \t\r\n", &last)) == NULL((void *)0)) |
290 | goto verify_failure; |
291 | if ((mp->val = strtok_r(NULL((void *)0), " \t\r\n", &last)) == NULL((void *)0)) |
292 | goto verify_failure; |
293 | |
294 | /* Convert file value to hex and compare. */ |
295 | atob8(filekey, mp->val); |
296 | if (memcmp(filekey, fkey, SKEY_BINKEY_SIZE8) != 0) |
297 | goto verify_failure; /* Wrong response */ |
298 | |
299 | /* |
300 | * Update key in database. |
301 | * XXX - check return values of things that write to disk. |
302 | */ |
303 | btoa8(mp->val,key); |
304 | mp->n--; |
305 | (void)fseek(mp->keyfile, 0L, SEEK_SET0); |
306 | (void)fprintf(mp->keyfile, "%s\n%s\n%d\n%s\n%s\n", mp->logname, |
307 | skey_get_algorithm(), mp->n, mp->seed, mp->val); |
308 | (void)fflush(mp->keyfile); |
309 | (void)ftruncate(fileno(mp->keyfile)(!__isthreaded ? ((mp->keyfile)->_file) : (fileno)(mp-> keyfile)), ftello(mp->keyfile)); |
310 | (void)fclose(mp->keyfile); |
311 | mp->keyfile = NULL((void *)0); |
312 | return (0); |
313 | |
314 | verify_failure: |
315 | (void)fclose(mp->keyfile); |
316 | mp->keyfile = NULL((void *)0); |
317 | return (-1); |
318 | } |
319 | |
320 | /* |
321 | * skey_haskey() |
322 | * |
323 | * Returns: 1 user doesn't exist, -1 file error, 0 user exists. |
324 | * |
325 | */ |
326 | int |
327 | skey_haskey(char *username) |
328 | { |
329 | struct skey skey; |
330 | int i; |
331 | |
332 | i = skeylookup(&skey, username); |
333 | if (skey.keyfile != NULL((void *)0)) { |
334 | fclose(skey.keyfile); |
335 | skey.keyfile = NULL((void *)0); |
336 | } |
337 | return (i); |
338 | } |
339 | |
340 | /* |
341 | * skey_keyinfo() |
342 | * |
343 | * Returns the current sequence number and |
344 | * seed for the passed user. |
345 | * |
346 | */ |
347 | char * |
348 | skey_keyinfo(char *username) |
349 | { |
350 | static char str[SKEY_MAX_CHALLENGE(11 + 6 + 16)]; |
351 | struct skey skey; |
352 | int i; |
353 | |
354 | i = skeychallenge(&skey, username, str); |
355 | if (i == -1) |
356 | return (0); |
357 | |
358 | if (skey.keyfile != NULL((void *)0)) { |
359 | fclose(skey.keyfile); |
360 | skey.keyfile = NULL((void *)0); |
361 | } |
362 | return (str); |
363 | } |
364 | |
365 | /* |
366 | * skey_passcheck() |
367 | * |
368 | * Check to see if answer is the correct one to the current |
369 | * challenge. |
370 | * |
371 | * Returns: 0 success, -1 failure |
372 | * |
373 | */ |
374 | int |
375 | skey_passcheck(char *username, char *passwd) |
376 | { |
377 | struct skey skey; |
378 | int i; |
379 | |
380 | i = skeylookup(&skey, username); |
381 | if (i == -1 || i == 1) |
382 | return (-1); |
383 | |
384 | if (skeyverify(&skey, passwd) == 0) |
385 | return (skey.n); |
386 | |
387 | return (-1); |
388 | } |
389 | |
390 | #define ROUND(x)(((x)[0] << 24) + (((x)[1]) << 16) + (((x)[2]) << 8) + ((x)[3])) (((x)[0] << 24) + (((x)[1]) << 16) + (((x)[2]) << 8) + \ |
391 | ((x)[3])) |
392 | |
393 | /* |
394 | * hash_collapse() |
395 | */ |
396 | static u_int32_t |
397 | hash_collapse(u_char *s) |
398 | { |
399 | int len, target; |
400 | u_int32_t i; |
401 | |
402 | if ((strlen(s) % sizeof(u_int32_t)) == 0) |
403 | target = strlen(s); /* Multiple of 4 */ |
404 | else |
405 | target = strlen(s) - (strlen(s) % sizeof(u_int32_t)); |
406 | |
407 | for (i = 0, len = 0; len < target; len += 4) |
408 | i ^= ROUND(s + len)(((s + len)[0] << 24) + (((s + len)[1]) << 16) + ( ((s + len)[2]) << 8) + ((s + len)[3])); |
409 | |
410 | return i; |
411 | } |
412 | |
413 | /* |
414 | * skey_fakeprompt() |
415 | * |
416 | * Generate a fake prompt for the specified user. |
417 | * |
418 | */ |
419 | static void |
420 | skey_fakeprompt(char *username, char *skeyprompt) |
421 | { |
422 | char secret[SKEY_MAX_SEED_LEN16], pbuf[SKEY_MAX_PW_LEN255+1], *p, *u; |
423 | u_char *up; |
424 | SHA1_CTX ctx; |
425 | u_int ptr; |
426 | int i; |
427 | |
428 | /* |
429 | * Base first 4 chars of seed on hostname. |
430 | * Add some filler for short hostnames if necessary. |
431 | */ |
432 | if (gethostname(pbuf, sizeof(pbuf)) == -1) |
433 | *(p = pbuf) = '.'; |
434 | else |
435 | for (p = pbuf; isalnum((unsigned char)*p); p++) |
436 | if (isalpha((unsigned char)*p) && |
437 | isupper((unsigned char)*p)) |
438 | *p = (char)tolower((unsigned char)*p); |
439 | if (*p && p - pbuf < 4) |
440 | (void)strncpy(p, "asjd", 4 - (p - pbuf)); |
441 | pbuf[4] = '\0'; |
442 | |
443 | /* Hash the username if possible */ |
444 | if ((up = SHA1Data(username, strlen(username), NULL((void *)0))) != NULL((void *)0)) { |
445 | /* Collapse the hash */ |
446 | ptr = hash_collapse(up); |
447 | explicit_bzero(up, strlen(up)); |
448 | |
449 | /* Put that in your pipe and smoke it */ |
450 | arc4random_buf(secret, sizeof(secret)); |
451 | |
452 | /* Hash secret value with username */ |
453 | SHA1Init(&ctx); |
454 | SHA1Update(&ctx, secret, sizeof(secret)); |
455 | SHA1Update(&ctx, username, strlen(username)); |
456 | SHA1End(&ctx, up); |
457 | |
458 | /* Zero out */ |
459 | explicit_bzero(secret, sizeof(secret)); |
460 | |
461 | /* Now hash the hash */ |
462 | SHA1Init(&ctx); |
463 | SHA1Update(&ctx, up, strlen(up)); |
464 | SHA1End(&ctx, up); |
465 | |
466 | ptr = hash_collapse(up + 4); |
467 | |
468 | for (i = 4; i < 9; i++) { |
469 | pbuf[i] = (ptr % 10) + '0'; |
470 | ptr /= 10; |
471 | } |
472 | pbuf[i] = '\0'; |
473 | |
474 | /* Sequence number */ |
475 | ptr = ((up[2] + up[3]) % 99) + 1; |
476 | |
477 | freezero(up, 20); /* SHA1 specific */ |
478 | |
479 | (void)snprintf(skeyprompt, SKEY_MAX_CHALLENGE(11 + 6 + 16), |
480 | "otp-%.*s %d %.*s", SKEY_MAX_HASHNAME_LEN6, |
481 | skey_get_algorithm(), ptr, SKEY_MAX_SEED_LEN16, pbuf); |
482 | } else { |
483 | /* Base last 8 chars of seed on username */ |
484 | u = username; |
485 | i = 8; |
486 | p = &pbuf[4]; |
487 | do { |
488 | if (*u == 0) { |
489 | /* Pad remainder with zeros */ |
490 | while (--i >= 0) |
491 | *p++ = '0'; |
492 | break; |
493 | } |
494 | |
495 | *p++ = (*u++ % 10) + '0'; |
496 | } while (--i != 0); |
497 | pbuf[12] = '\0'; |
498 | |
499 | (void)snprintf(skeyprompt, SKEY_MAX_CHALLENGE(11 + 6 + 16), |
500 | "otp-%.*s %d %.*s", SKEY_MAX_HASHNAME_LEN6, |
501 | skey_get_algorithm(), 99, SKEY_MAX_SEED_LEN16, pbuf); |
502 | } |
503 | } |
504 | |
505 | /* |
506 | * skey_authenticate() |
507 | * |
508 | * Used when calling program will allow input of the user's |
509 | * response to the challenge. |
510 | * |
511 | * Returns: 0 success, -1 failure |
512 | * |
513 | */ |
514 | int |
515 | skey_authenticate(char *username) |
516 | { |
517 | char pbuf[SKEY_MAX_PW_LEN255+1], skeyprompt[SKEY_MAX_CHALLENGE(11 + 6 + 16)+1]; |
518 | struct skey skey; |
519 | int i; |
520 | |
521 | /* Get the S/Key challenge (may be fake) */ |
522 | i = skeychallenge(&skey, username, skeyprompt); |
523 | (void)fprintf(stderr(&__sF[2]), "%s\nResponse: ", skeyprompt); |
524 | (void)fflush(stderr(&__sF[2])); |
525 | |
526 | /* Time out on user input after 2 minutes */ |
527 | tgetline(fileno(stdin)(!__isthreaded ? (((&__sF[0]))->_file) : (fileno)((& __sF[0]))), pbuf, sizeof(pbuf), 120); |
528 | sevenbit(pbuf); |
529 | (void)rewind(stdin(&__sF[0])); |
530 | |
531 | /* Is it a valid response? */ |
532 | if (i == 0 && skeyverify(&skey, pbuf) == 0) { |
533 | if (skey.n < 5) { |
534 | (void)fprintf(stderr(&__sF[2]), |
535 | "\nWarning! Key initialization needed soon. (%d logins left)\n", |
536 | skey.n); |
537 | } |
538 | return (0); |
539 | } |
540 | return (-1); |
541 | } |
542 | |
543 | /* |
544 | * Unlock current entry in the One-time Password database. |
545 | * |
546 | * Return codes: |
547 | * -1: unable to lock the record |
548 | * 0: record was successfully unlocked |
549 | */ |
550 | int |
551 | skey_unlock(struct skey *mp) |
552 | { |
553 | if (mp->logname == NULL((void *)0) || mp->keyfile == NULL((void *)0)) |
554 | return (-1); |
555 | |
556 | return (flock(fileno(mp->keyfile)(!__isthreaded ? ((mp->keyfile)->_file) : (fileno)(mp-> keyfile)), LOCK_UN0x08)); |
557 | } |
558 | |
559 | /* |
560 | * Get a line of input (optionally timing out) and place it in buf. |
561 | */ |
562 | static char * |
563 | tgetline(int fd, char *buf, size_t bufsiz, int timeout) |
564 | { |
565 | struct pollfd pfd[1]; |
566 | size_t left; |
567 | char c, *cp; |
568 | ssize_t ss; |
569 | int n; |
570 | |
571 | if (bufsiz == 0) |
572 | return (NULL((void *)0)); /* sanity */ |
573 | |
574 | cp = buf; |
575 | left = bufsiz; |
576 | |
577 | /* |
578 | * Timeout of <= 0 means no timeout. |
579 | */ |
580 | if (timeout > 0) { |
581 | timeout *= 1000; /* convert to milliseconds */ |
582 | |
583 | pfd[0].fd = fd; |
584 | pfd[0].events = POLLIN0x0001; |
585 | while (--left) { |
586 | /* Poll until we are ready or we time out */ |
587 | while ((n = poll(pfd, 1, timeout)) == -1 && |
588 | (errno(*__errno()) == EINTR4 || errno(*__errno()) == EAGAIN35)) |
589 | ; |
590 | if (n <= 0 || |
591 | (pfd[0].revents & (POLLERR0x0008|POLLHUP0x0010|POLLNVAL0x0020))) |
592 | break; /* timeout or error */ |
593 | |
594 | /* Read a character, exit loop on error, EOF or EOL */ |
595 | ss = read(fd, &c, 1); |
596 | if (ss != 1 || c == '\n' || c == '\r') |
597 | break; |
598 | *cp++ = c; |
599 | } |
600 | } else { |
601 | /* Keep reading until out of space, EOF, error, or newline */ |
602 | while (--left && read(fd, &c, 1) == 1 && c != '\n' && c != '\r') |
603 | *cp++ = c; |
604 | } |
605 | *cp = '\0'; |
606 | |
607 | return (cp == buf ? NULL((void *)0) : buf); |
608 | } |