Patrick Welche <prlw1@cam.ac.uk>
[netbsd-mini2440.git] / lib / libskey / skeylogin.c
bloba822017e1497bd95d6c5bc4478015c61deacf179
1 /* $NetBSD: skeylogin.c,v 1.18 2003/03/09 00:46:07 lukem Exp $ */
3 /* S/KEY v1.1b (skeylogin.c)
5 * Authors:
6 * Neil M. Haller <nmh@thumper.bellcore.com>
7 * Philip R. Karn <karn@chicago.qualcomm.com>
8 * John S. Walden <jsw@thumper.bellcore.com>
9 * Scott Chasin <chasin@crimelab.com>
11 * Modifications:
12 * Todd C. Miller <Todd.Miller@courtesan.com>
13 * Angelos D. Keromytis <adk@adk.gr>
15 * S/KEY verification check, lookups, and authentication.
18 #include <sys/cdefs.h>
19 __RCSID("$NetBSD: skeylogin.c,v 1.18 2003/03/09 00:46:07 lukem Exp $");
21 #include <sys/param.h>
22 #include <sys/stat.h>
23 #include <sys/time.h>
24 #include <sys/resource.h>
25 #include <sys/types.h>
27 #include <ctype.h>
28 #include <err.h>
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <paths.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36 #include <unistd.h>
38 #include "skey.h"
40 #define OTP_FMT "otp-%.*s %d %.*s"
42 /* Issue a skey challenge for user 'name'. If successful,
43 * fill in the caller's skey structure and return 0. If unsuccessful
44 * (e.g., if name is unknown) return -1.
46 * The file read/write pointer is left at the start of the
47 * record.
49 int getskeyprompt(struct skey *mp, char *name, char *prompt)
51 int rval;
53 sevenbit(name);
54 rval = skeylookup(mp, name);
56 *prompt = '\0';
57 switch (rval) {
58 case -1: /* File error */
59 return -1;
60 case 0: /* Lookup succeeded, return challenge */
61 sprintf(prompt, OTP_FMT "\n",
62 SKEY_MAX_HASHNAME_LEN, skey_get_algorithm(),
63 mp->n - 1, SKEY_MAX_SEED_LEN, mp->seed);
64 return 0;
65 case 1: /* User not found */
66 fclose(mp->keyfile);
67 mp->keyfile = NULL;
68 return -1;
70 return -1; /* Can't happen */
73 /* Return a skey challenge string for user 'name'. If successful,
74 * fill in the caller's skey structure and return 0. If unsuccessful
75 * (e.g., if name is unknown) return -1.
77 * The file read/write pointer is left at the start of the
78 * record.
80 int skeychallenge(struct skey *mp, const char *name, char *ss, size_t sslen)
82 int rval;
84 rval = skeylookup(mp, name);
86 *ss = '\0';
87 switch(rval){
88 case -1: /* File error */
89 return -1;
90 case 0: /* Lookup succeeded, issue challenge */
91 snprintf(ss, sslen, OTP_FMT, SKEY_MAX_HASHNAME_LEN,
92 skey_get_algorithm(), mp->n - 1,
93 SKEY_MAX_SEED_LEN, mp->seed);
94 return 0;
95 case 1: /* User not found */
96 fclose(mp->keyfile);
97 mp->keyfile = NULL;
98 return -1;
100 return -1; /* Can't happen */
103 static FILE *openSkey(void)
105 struct stat statbuf;
106 FILE *keyfile = NULL;
108 /* Open _PATH_SKEYKEYS if it exists, else return an error */
109 if (stat(_PATH_SKEYKEYS, &statbuf) == 0 &&
110 (keyfile = fopen(_PATH_SKEYKEYS, "r+"))) {
111 if ((statbuf.st_mode & 0007777) != 0600)
112 fchmod(fileno(keyfile), 0600);
113 } else {
114 keyfile = NULL;
117 return keyfile;
120 /* Find an entry in the One-time Password database.
121 * Return codes:
122 * -1: error in opening database
123 * 0: entry found, file R/W pointer positioned at beginning of record
124 * 1: entry not found, file R/W pointer positioned at EOF
126 int skeylookup(struct skey *mp, const char *name)
128 int found = 0;
129 long recstart = 0;
130 const char *ht = NULL;
131 char *last;
133 if(!(mp->keyfile = openSkey()))
134 return(-1);
136 /* Look up user name in database */
137 while (!feof(mp->keyfile)) {
138 char *cp;
140 recstart = ftell(mp->keyfile);
141 mp->recstart = recstart;
142 if (fgets(mp->buf, sizeof(mp->buf), mp->keyfile) != mp->buf)
143 break;
145 rip(mp->buf);
146 if (mp->buf[0] == '#')
147 continue; /* Comment */
148 if ((mp->logname = strtok_r(mp->buf, " \t", &last)) == NULL)
149 continue;
150 if ((cp = strtok_r(NULL, " \t", &last)) == NULL)
151 continue;
152 /* Save hash type if specified, else use md4 */
153 if (isalpha((u_char) *cp)) {
154 ht = cp;
155 if ((cp = strtok_r(NULL, " \t", &last)) == NULL)
156 continue;
157 } else {
158 ht = "md4";
160 mp->n = atoi(cp);
161 if ((mp->seed = strtok_r(NULL, " \t", &last)) == NULL)
162 continue;
163 if ((mp->val = strtok_r(NULL, " \t", &last)) == NULL)
164 continue;
165 if (strcmp(mp->logname, name) == 0) {
166 found = 1;
167 break;
170 if (found) {
171 fseek(mp->keyfile, recstart, SEEK_SET);
172 /* Set hash type */
173 if (ht && skey_set_algorithm(ht) == NULL) {
174 warnx("Unknown hash algorithm %s, using %s", ht,
175 skey_get_algorithm());
177 return(0);
178 } else {
179 return(1);
183 /* Get the next entry in the One-time Password database.
184 * Return codes:
185 * -1: error in opening database
186 * 0: next entry found and stored in mp
187 * 1: no more entries, file R/W pointer positioned at EOF
189 int skeygetnext(struct skey *mp)
191 long recstart = 0;
192 char *last;
194 /* Open _PATH_SKEYKEYS if it exists, else return an error */
195 if (mp->keyfile == NULL) {
196 if(!(mp->keyfile = openSkey()))
197 return(-1);
200 /* Look up next user in database */
201 while (!feof(mp->keyfile)) {
202 char *cp;
204 recstart = ftell(mp->keyfile);
205 mp->recstart = recstart;
206 if (fgets(mp->buf, sizeof(mp->buf), mp->keyfile) != mp->buf)
207 break;
208 rip(mp->buf);
209 if (mp->buf[0] == '#')
210 continue; /* Comment */
211 if ((mp->logname = strtok_r(mp->buf, " \t", &last)) == NULL)
212 continue;
213 if ((cp = strtok_r(NULL, " \t", &last)) == NULL)
214 continue;
215 /* Save hash type if specified, else use md4 */
216 if (isalpha((u_char) *cp)) {
217 if ((cp = strtok_r(NULL, " \t", &last)) == NULL)
218 continue;
220 mp->n = atoi(cp);
221 if ((mp->seed = strtok_r(NULL, " \t", &last)) == NULL)
222 continue;
223 if ((mp->val = strtok_r(NULL, " \t", &last)) == NULL)
224 continue;
225 /* Got a real entry */
226 break;
228 return(feof(mp->keyfile));
231 /* Verify response to a s/key challenge.
233 * Return codes:
234 * -1: Error of some sort; database unchanged
235 * 0: Verify successful, database updated
236 * 1: Verify failed, database unchanged
238 * The database file is always closed by this call.
241 int skeyverify(struct skey *mp, char *response)
243 char key[SKEY_BINKEY_SIZE];
244 char fkey[SKEY_BINKEY_SIZE];
245 char filekey[SKEY_BINKEY_SIZE];
246 time_t now;
247 struct tm *tm;
248 char tbuf[27];
249 char *cp, *last;
250 int i, rval;
252 time(&now);
253 tm = localtime(&now);
254 strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm);
256 if (response == NULL) {
257 fclose(mp->keyfile);
258 mp->keyfile = NULL;
259 return -1;
261 rip(response);
263 /* Convert response to binary */
264 if (etob(key, response) != 1 && atob8(key, response) != 0) {
265 /* Neither english words or ascii hex */
266 fclose(mp->keyfile);
267 mp->keyfile = NULL;
268 return -1;
271 /* Compute fkey = f(key) */
272 memcpy(fkey, key, sizeof(key));
273 fflush(stdout);
275 f(fkey);
278 * Obtain an exclusive lock on the key file so the same password
279 * cannot be used twice to get in to the system.
281 for (i = 0; i < 300; i++) {
282 if ((rval = flock(fileno(mp->keyfile), LOCK_EX|LOCK_NB)) == 0 ||
283 errno != EWOULDBLOCK)
284 break;
285 usleep(100000); /* Sleep for 0.1 seconds */
287 if (rval == -1) { /* Can't get exclusive lock */
288 errno = EAGAIN;
289 return(-1);
292 /* Reread the file record NOW */
294 fseek(mp->keyfile, mp->recstart, SEEK_SET);
295 if (fgets(mp->buf, sizeof(mp->buf), mp->keyfile) != mp->buf) {
296 fclose(mp->keyfile);
297 mp->keyfile = NULL;
298 return -1;
300 rip(mp->buf);
301 if ((mp->logname = strtok_r(mp->buf, " \t", &last)) == NULL)
302 goto verify_failure;
303 if ((cp = strtok_r(NULL, " \t", &last)) == NULL)
304 goto verify_failure;
305 if (isalpha((u_char) *cp))
306 if ((cp = strtok_r(NULL, " \t", &last)) == NULL)
307 goto verify_failure;
308 if ((mp->seed = strtok_r(NULL, " \t", &last)) == NULL)
309 goto verify_failure;
310 if ((mp->val = strtok_r(NULL, " \t", &last)) == NULL)
311 goto verify_failure;
312 /* And convert file value to hex for comparison */
313 atob8(filekey, mp->val);
315 /* Do actual comparison */
316 if (memcmp(filekey, fkey, SKEY_BINKEY_SIZE) != 0) {
317 /* Wrong response */
318 fclose(mp->keyfile);
319 mp->keyfile = NULL;
320 return 1;
324 * Update key in database by overwriting entire record. Note
325 * that we must write exactly the same number of bytes as in
326 * the original record (note fixed width field for N)
328 btoa8(mp->val, key);
329 mp->n--;
330 fseek(mp->keyfile, mp->recstart, SEEK_SET);
331 /* Don't save algorithm type for md4 (keep record length same) */
332 if (strcmp(skey_get_algorithm(), "md4") == 0)
333 (void)fprintf(mp->keyfile, "%s %04d %-16s %s %-21s\n",
334 mp->logname, mp->n, mp->seed, mp->val, tbuf);
335 else
336 (void)fprintf(mp->keyfile, "%s %s %04d %-16s %s %-21s\n",
337 mp->logname, skey_get_algorithm(), mp->n,
338 mp->seed, mp->val, tbuf);
340 fclose(mp->keyfile);
341 mp->keyfile = NULL;
342 return 0;
344 verify_failure:
345 fclose(mp->keyfile);
346 mp->keyfile = NULL;
347 return -1;
351 /* Returns: 1 user doesnt exist, -1 file error, 0 user exists. */
353 int skey_haskey(const char *username)
355 struct skey skey;
356 int i;
358 i = skeylookup(&skey, username);
360 if (skey.keyfile != NULL) {
361 fclose(skey.keyfile);
362 skey.keyfile = NULL;
364 return(i);
368 * Returns the current sequence number and
369 * seed for the passed user.
371 const char *skey_keyinfo(const char *username)
373 int i;
374 static char str[SKEY_MAX_CHALLENGE];
375 struct skey skey;
377 i = skeychallenge(&skey, username, str, sizeof str);
378 if (i == -1)
379 return 0;
381 if (skey.keyfile != NULL) {
382 fclose(skey.keyfile);
383 skey.keyfile = NULL;
385 return str;
389 * Check to see if answer is the correct one to the current
390 * challenge.
392 * Returns: 0 success, -1 failure
395 int skey_passcheck(const char *username, char *passwd)
397 int i;
398 struct skey skey;
400 i = skeylookup (&skey, username);
401 if (i == -1 || i == 1)
402 return -1;
404 if (skeyverify (&skey, passwd) == 0)
405 return skey.n;
407 return -1;
410 #if DO_FAKE_CHALLENGE
411 #define ROUND(x) (((x)[0] << 24) + (((x)[1]) << 16) + (((x)[2]) << 8) + \
412 ((x)[3]))
415 * hash_collapse()
417 static u_int32_t hash_collapse(u_char *s)
419 int len, target;
420 u_int32_t i;
421 int slen;
423 slen = strlen((char *)s);
424 if ((slen % sizeof(u_int32_t)) == 0)
425 target = slen; /* Multiple of 4 */
426 else
427 target = slen - slen % sizeof(u_int32_t);
429 for (i = 0, len = 0; len < target; len += 4)
430 i ^= ROUND(s + len);
432 return i;
434 #endif
437 * Used when calling program will allow input of the user's
438 * response to the challenge.
440 * Returns: 0 success, -1 failure
443 int skey_authenticate(const char *username)
445 int i;
446 char pbuf[SKEY_MAX_PW_LEN+1], skeyprompt[SKEY_MAX_CHALLENGE+1];
447 struct skey skey;
448 #if DO_FAKE_CHALLENGE
449 u_int ptr;
450 u_char hseed[SKEY_MAX_SEED_LEN], flg = 1, *up;
451 size_t secretlen;
452 struct skey skey;
453 SHA1_CTX ctx;
454 #endif
455 /* Attempt a S/Key challenge */
456 i = skeychallenge(&skey, username, skeyprompt, sizeof skeyprompt);
458 #if DO_FAKE_CHALLENGE
459 /* Cons up a fake prompt if no entry in keys file */
460 if (i != 0) {
461 char *p, *u;
464 * Base first 4 chars of seed on hostname.
465 * Add some filler for short hostnames if necessary.
467 if (gethostname(pbuf, sizeof(pbuf)) == -1)
468 *(p = pbuf) = '.';
469 else
470 for (p = pbuf; *p && isalnum((u_char) *p); p++)
471 if (isalpha((u_char)*p) && isupper((u_char)*p))
472 *p = tolower((u_char)*p);
473 if (*p && pbuf - p < 4)
474 (void)strncpy(p, "asjd", 4 - (pbuf - p));
475 pbuf[4] = '\0';
477 /* Hash the username if possible */
478 if ((up = SHA1Data(username, strlen(username), NULL)) != NULL) {
479 struct stat sb;
480 time_t t;
481 int fd;
483 /* Collapse the hash */
484 ptr = hash_collapse(up);
485 memset(up, 0, strlen(up));
487 /* See if the random file's there, else use ctime */
488 if ((fd = open(_SKEY_RAND_FILE_PATH_, O_RDONLY)) != -1
489 && fstat(fd, &sb) == 0 &&
490 sb.st_size > (off_t)SKEY_MAX_SEED_LEN &&
491 lseek(fd, ptr % (sb.st_size - SKEY_MAX_SEED_LEN),
492 SEEK_SET) != -1 && read(fd, hseed,
493 SKEY_MAX_SEED_LEN) == SKEY_MAX_SEED_LEN) {
494 close(fd);
495 fd = -1;
496 secret = hseed;
497 secretlen = SKEY_MAX_SEED_LEN;
498 flg = 0;
499 } else if (!stat(_PATH_MEM, &sb) || !stat("/", &sb)) {
500 t = sb.st_ctime;
501 secret = ctime(&t);
502 secretlen = strlen(secret);
503 flg = 0;
505 if (fd != -1)
506 close(fd);
509 /* Put that in your pipe and smoke it */
510 if (flg == 0) {
511 /* Hash secret value with username */
512 SHA1Init(&ctx);
513 SHA1Update(&ctx, secret, secretlen);
514 SHA1Update(&ctx, username, strlen(username));
515 SHA1End(&ctx, up);
517 /* Zero out */
518 memset(secret, 0, secretlen);
520 /* Now hash the hash */
521 SHA1Init(&ctx);
522 SHA1Update(&ctx, up, strlen(up));
523 SHA1End(&ctx, up);
525 ptr = hash_collapse(up + 4);
527 for (i = 4; i < 9; i++) {
528 pbuf[i] = (ptr % 10) + '0';
529 ptr /= 10;
531 pbuf[i] = '\0';
533 /* Sequence number */
534 ptr = ((up[2] + up[3]) % 99) + 1;
536 memset(up, 0, 20); /* SHA1 specific */
537 free(up);
539 (void)sprintf(skeyprompt,
540 "otp-%.*s %d %.*s",
541 SKEY_MAX_HASHNAME_LEN,
542 skey_get_algorithm(),
543 ptr, SKEY_MAX_SEED_LEN,
544 pbuf);
545 } else {
546 /* Base last 8 chars of seed on username */
547 u = username;
548 i = 8;
549 p = &pbuf[4];
550 do {
551 if (*u == 0) {
552 /* Pad remainder with zeros */
553 while (--i >= 0)
554 *p++ = '0';
555 break;
558 *p++ = (*u++ % 10) + '0';
559 } while (--i != 0);
560 pbuf[12] = '\0';
562 (void)sprintf(skeyprompt, "otp-%.*s %d %.*s",
563 SKEY_MAX_HASHNAME_LEN,
564 skey_get_algorithm(),
565 99, SKEY_MAX_SEED_LEN, pbuf);
568 #endif
570 fprintf(stderr, "[%s]\n", skeyprompt);
571 fflush(stderr);
573 fputs("Response: ", stderr);
574 readskey(pbuf, sizeof(pbuf));
576 /* Is it a valid response? */
577 if (i == 0 && skeyverify(&skey, pbuf) == 0) {
578 if (skey.n < 5) {
579 fprintf(stderr,
580 "\nWarning! Key initialization needed soon. (%d logins left)\n",
581 skey.n);
583 return 0;
585 return -1;
588 /* Comment out user's entry in the s/key database
590 * Return codes:
591 * -1: Write error; database unchanged
592 * 0: Database updated
594 * The database file is always closed by this call.
597 /* ARGSUSED */
598 int skeyzero(struct skey *mp, char *response)
601 * Seek to the right place and write comment character
602 * which effectively zero's out the entry.
604 fseek(mp->keyfile, mp->recstart, SEEK_SET);
605 if (fputc('#', mp->keyfile) == EOF) {
606 fclose(mp->keyfile);
607 mp->keyfile = NULL;
608 return(-1);
611 fclose(mp->keyfile);
612 mp->keyfile = NULL;
613 return(0);