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