Home | History | Annotate | Line # | Download | only in libskey
      1 /*	$NetBSD: skeylogin.c,v 1.19 2005/02/04 16:13:14 perry 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/cdefs.h>
     19 __RCSID("$NetBSD: skeylogin.c,v 1.19 2005/02/04 16:13:14 perry Exp $");
     20 
     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>
     26 
     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>
     37 
     38 #include "skey.h"
     39 
     40 #define OTP_FMT "otp-%.*s %d %.*s"
     41 
     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.
     45  *
     46  * The file read/write pointer is left at the start of the
     47  * record.
     48  */
     49 int getskeyprompt(struct skey *mp, char *name, char *prompt)
     50 {
     51 	int rval;
     52 
     53 	sevenbit(name);
     54 	rval = skeylookup(mp, name);
     55 
     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;
     69 	}
     70 	return -1;	/* Can't happen */
     71 }
     72 
     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.
     76  *
     77  * The file read/write pointer is left at the start of the
     78  * record.
     79  */
     80 int skeychallenge(struct skey *mp, const char *name, char *ss, size_t sslen)
     81 {
     82 	int rval;
     83 
     84 	rval = skeylookup(mp, name);
     85 
     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;
     99 	}
    100 	return -1;	/* Can't happen */
    101 }
    102 
    103 static FILE *openSkey(void)
    104 {
    105 	struct stat statbuf;
    106 	FILE *keyfile = NULL;
    107 
    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;
    115 	}
    116 
    117 	return keyfile;
    118 }
    119 
    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
    125  */
    126 int skeylookup(struct skey *mp, const char *name)
    127 {
    128 	int found = 0;
    129 	long recstart = 0;
    130 	const char *ht = NULL;
    131 	char *last;
    132 
    133 	if(!(mp->keyfile = openSkey()))
    134 		return(-1);
    135 
    136 	/* Look up user name in database */
    137 	while (!feof(mp->keyfile)) {
    138 		char *cp;
    139 
    140 		recstart = ftell(mp->keyfile);
    141 		mp->recstart = recstart;
    142 		if (fgets(mp->buf, sizeof(mp->buf), mp->keyfile) != mp->buf)
    143 			break;
    144 
    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";
    159 		}
    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;
    168 		}
    169 	}
    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());
    176 		}
    177 		return(0);
    178 	} else {
    179         	return(1);
    180 	}
    181 }
    182 
    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
    188  */
    189 int skeygetnext(struct skey *mp)
    190 {
    191 	long recstart = 0;
    192 	char *last;
    193 
    194 	/* Open _PATH_SKEYKEYS if it exists, else return an error */
    195 	if (mp->keyfile == NULL) {
    196 		if(!(mp->keyfile = openSkey()))
    197 			return(-1);
    198 	}
    199 
    200 	/* Look up next user in database */
    201 	while (!feof(mp->keyfile)) {
    202 		char *cp;
    203 
    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;
    219 		}
    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;
    227 	}
    228 	return(feof(mp->keyfile));
    229 }
    230 
    231 /* Verify response to a s/key challenge.
    232  *
    233  * Return codes:
    234  * -1: Error of some sort; database unchanged
    235  *  0:  Verify successful, database updated
    236  *  1:  Verify failed, database unchanged
    237  *
    238  * The database file is always closed by this call.
    239  */
    240 
    241 int skeyverify(struct skey *mp, char *response)
    242 {
    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;
    251 
    252 	time(&now);
    253 	tm = localtime(&now);
    254 	strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm);
    255 
    256 	if (response == NULL) {
    257 		fclose(mp->keyfile);
    258 		mp->keyfile = NULL;
    259 		return -1;
    260 	}
    261 	rip(response);
    262 
    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;
    269 	}
    270 
    271 	/* Compute fkey = f(key) */
    272 	memcpy(fkey, key, sizeof(key));
    273         fflush(stdout);
    274 
    275 	f(fkey);
    276 
    277 	/*
    278 	 * Obtain an exclusive lock on the key file so the same password
    279 	 * cannot be used twice to get in to the system.
    280 	 */
    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 */
    286 	}
    287 	if (rval == -1) {			/* Can't get exclusive lock */
    288 		errno = EAGAIN;
    289 		return(-1);
    290 	}
    291 
    292 	/* Reread the file record NOW */
    293 
    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;
    299 	}
    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);
    314 
    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;
    321 	}
    322 
    323 	/*
    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)
    327 	 */
    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);
    339 
    340 	fclose(mp->keyfile);
    341 	mp->keyfile = NULL;
    342 	return 0;
    343 
    344   verify_failure:
    345 	fclose(mp->keyfile);
    346 	mp->keyfile = NULL;
    347 	return -1;
    348 }
    349 
    350 
    351 /* Returns: 1 user doesnt exist, -1 file error, 0 user exists. */
    352 
    353 int skey_haskey(const char *username)
    354 {
    355 	struct skey skey;
    356 	int i;
    357 
    358 	i = skeylookup(&skey, username);
    359 
    360 	if (skey.keyfile != NULL) {
    361 		fclose(skey.keyfile);
    362 		skey.keyfile = NULL;
    363 	}
    364 	return(i);
    365 }
    366 
    367 /*
    368  * Returns the current sequence number and
    369  * seed for the passed user.
    370  */
    371 const char *skey_keyinfo(const char *username)
    372 {
    373 	int i;
    374 	static char str[SKEY_MAX_CHALLENGE];
    375 	struct skey skey;
    376 
    377 	i = skeychallenge(&skey, username, str, sizeof str);
    378 	if (i == -1)
    379 		return 0;
    380 
    381 	if (skey.keyfile != NULL) {
    382 		fclose(skey.keyfile);
    383 		skey.keyfile = NULL;
    384 	}
    385 	return str;
    386 }
    387 
    388 /*
    389  * Check to see if answer is the correct one to the current
    390  * challenge.
    391  *
    392  * Returns: 0 success, -1 failure
    393  */
    394 
    395 int skey_passcheck(const char *username, char *passwd)
    396 {
    397 	int i;
    398 	struct skey skey;
    399 
    400 	i = skeylookup (&skey, username);
    401 	if (i == -1 || i == 1)
    402 		return -1;
    403 
    404 	if (skeyverify (&skey, passwd) == 0)
    405 		return skey.n;
    406 
    407 	return -1;
    408 }
    409 
    410 #if DO_FAKE_CHALLENGE
    411 #define ROUND(x)   (((x)[0] << 24) + (((x)[1]) << 16) + (((x)[2]) << 8) + \
    412 		    ((x)[3]))
    413 
    414 /*
    415  * hash_collapse()
    416  */
    417 static u_int32_t hash_collapse(u_char *s)
    418 {
    419         int len, target;
    420 	u_int32_t i;
    421 	int slen;
    422 
    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);
    428 
    429 	for (i = 0, len = 0; len < target; len += 4)
    430         	i ^= ROUND(s + len);
    431 
    432 	return i;
    433 }
    434 #endif
    435 
    436 /*
    437  * Used when calling program will allow input of the user's
    438  * response to the challenge.
    439  *
    440  * Returns: 0 success, -1 failure
    441  */
    442 
    443 int skey_authenticate(const char *username)
    444 {
    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);
    457 
    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;
    462 
    463 		/*
    464 		 * Base first 4 chars of seed on hostname.
    465 		 * Add some filler for short hostnames if necessary.
    466 		 */
    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';
    476 
    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;
    482 
    483 			/* Collapse the hash */
    484 			ptr = hash_collapse(up);
    485 			memset(up, 0, strlen(up));
    486 
    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;
    504 			}
    505 			if (fd != -1)
    506 				close(fd);
    507 		}
    508 
    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);
    516 
    517 			/* Zero out */
    518 			memset(secret, 0, secretlen);
    519 
    520 			/* Now hash the hash */
    521 			SHA1Init(&ctx);
    522 			SHA1Update(&ctx, up, strlen(up));
    523 			SHA1End(&ctx, up);
    524 
    525 			ptr = hash_collapse(up + 4);
    526 
    527 			for (i = 4; i < 9; i++) {
    528 				pbuf[i] = (ptr % 10) + '0';
    529 				ptr /= 10;
    530 			}
    531 			pbuf[i] = '\0';
    532 
    533 			/* Sequence number */
    534 			ptr = ((up[2] + up[3]) % 99) + 1;
    535 
    536 			memset(up, 0, 20); /* SHA1 specific */
    537 			free(up);
    538 
    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;
    556 				}
    557 
    558 				*p++ = (*u++ % 10) + '0';
    559 			} while (--i != 0);
    560 			pbuf[12] = '\0';
    561 
    562 			(void)sprintf(skeyprompt, "otp-%.*s %d %.*s",
    563 				      SKEY_MAX_HASHNAME_LEN,
    564 				      skey_get_algorithm(),
    565 				      99, SKEY_MAX_SEED_LEN, pbuf);
    566 		}
    567 	}
    568 #endif
    569 
    570 	fprintf(stderr, "[%s]\n", skeyprompt);
    571 	fflush(stderr);
    572 
    573 	fputs("Response: ", stderr);
    574 	readskey(pbuf, sizeof(pbuf));
    575 
    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);
    582 		}
    583 		return 0;
    584 	}
    585 	return -1;
    586 }
    587 
    588 /* Comment out user's entry in the s/key database
    589  *
    590  * Return codes:
    591  * -1: Write error; database unchanged
    592  *  0:  Database updated
    593  *
    594  * The database file is always closed by this call.
    595  */
    596 
    597 /* ARGSUSED */
    598 int skeyzero(struct skey *mp, char *response)
    599 {
    600 	/*
    601 	 * Seek to the right place and write comment character
    602 	 * which effectively zero's out the entry.
    603 	 */
    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);
    609 	}
    610 
    611 	fclose(mp->keyfile);
    612 	mp->keyfile = NULL;
    613 	return(0);
    614 }
    615