skeylogin.c revision 1.23
1/* S/KEY v1.1b (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 *
9 * Modifications:
10 *          Todd C. Miller <Todd.Miller@courtesan.com>
11 *
12 * S/KEY verification check, lookups, and authentication.
13 *
14 * $OpenBSD: skeylogin.c,v 1.23 1998/02/24 20:52:48 millert Exp $
15 */
16
17#include <sys/param.h>
18#include <sys/file.h>
19#ifdef	QUOTA
20#include <sys/quota.h>
21#endif
22#include <sys/stat.h>
23#include <sys/time.h>
24#include <sys/resource.h>
25#include <sys/types.h>
26#include <sys/stat.h>
27
28#include <ctype.h>
29#include <err.h>
30#include <errno.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
40char *skipspace __P((char *));
41int skeylookup __P((struct skey *, char *));
42
43/* Issue a skey challenge 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
51getskeyprompt(mp, name, prompt)
52	struct skey *mp;
53	char *name;
54	char *prompt;
55{
56	int rval;
57
58	sevenbit(name);
59	rval = skeylookup(mp, name);
60	(void)strcpy(prompt, "otp-md0 55 latour1\n");
61	switch (rval) {
62	case -1:	/* File error */
63		return(-1);
64	case 0:		/* Lookup succeeded, return challenge */
65		(void)sprintf(prompt, "otp-%.*s %d %.*s\n",
66			      SKEY_MAX_HASHNAME_LEN, skey_get_algorithm(),
67			      mp->n - 1, SKEY_MAX_SEED_LEN, mp->seed);
68		return(0);
69	case 1:		/* User not found */
70		(void)fclose(mp->keyfile);
71		return(-1);
72	}
73	return(-1);	/* Can't happen */
74}
75
76/* Return  a skey challenge string for user 'name'. If successful,
77 * fill in the caller's skey structure and return(0). If unsuccessful
78 * (e.g., if name is unknown) return(-1).
79 *
80 * The file read/write pointer is left at the start of the
81 * record.
82 */
83int
84skeychallenge(mp, name, ss)
85	struct skey *mp;
86	char *name;
87	char *ss;
88{
89	int rval;
90
91	rval = skeylookup(mp,name);
92	switch(rval){
93	case -1:	/* File error */
94		return(-1);
95	case 0:		/* Lookup succeeded, issue challenge */
96		(void)sprintf(ss, "otp-%.*s %d %.*s", SKEY_MAX_HASHNAME_LEN,
97			      skey_get_algorithm(), mp->n - 1,
98			      SKEY_MAX_SEED_LEN, mp->seed);
99		return(0);
100	case 1:		/* User not found */
101		(void)fclose(mp->keyfile);
102		return(-1);
103	}
104	return(-1);	/* Can't happen */
105}
106
107/* Find an entry in the One-time Password database.
108 * Return codes:
109 * -1: error in opening database
110 *  0: entry found, file R/W pointer positioned at beginning of record
111 *  1: entry not found, file R/W pointer positioned at EOF
112 */
113int
114skeylookup(mp, name)
115	struct skey *mp;
116	char *name;
117{
118	int found = 0;
119	long recstart = 0;
120	char *cp, *ht = NULL;
121	struct stat statbuf;
122
123	/* Open _PATH_SKEYKEYS if it exists, else return an error */
124	if (stat(_PATH_SKEYKEYS, &statbuf) == 0 &&
125	    (mp->keyfile = fopen(_PATH_SKEYKEYS, "r+")) != NULL) {
126		if ((statbuf.st_mode & 0007777) != 0600)
127			fchmod(fileno(mp->keyfile), 0600);
128	} else {
129		return(-1);
130	}
131
132	/* Look up user name in database */
133	while (!feof(mp->keyfile)) {
134		recstart = ftell(mp->keyfile);
135		mp->recstart = recstart;
136		if (fgets(mp->buf, sizeof(mp->buf), mp->keyfile) != mp->buf)
137			break;
138		rip(mp->buf);
139		if (mp->buf[0] == '#')
140			continue;	/* Comment */
141		if ((mp->logname = strtok(mp->buf, " \t")) == NULL)
142			continue;
143		if ((cp = strtok(NULL, " \t")) == NULL)
144			continue;
145		/* Save hash type if specified, else use md4 */
146		if (isalpha(*cp)) {
147			ht = cp;
148			if ((cp = strtok(NULL, " \t")) == NULL)
149				continue;
150		} else {
151			ht = "md4";
152		}
153		mp->n = atoi(cp);
154		if ((mp->seed = strtok(NULL, " \t")) == NULL)
155			continue;
156		if ((mp->val = strtok(NULL, " \t")) == NULL)
157			continue;
158		if (strcmp(mp->logname, name) == 0) {
159			found = 1;
160			break;
161		}
162	}
163	if (found) {
164		(void)fseek(mp->keyfile, recstart, SEEK_SET);
165		/* Set hash type */
166		if (ht && skey_set_algorithm(ht) == NULL) {
167			warnx("Unknown hash algorithm %s, using %s", ht,
168			      skey_get_algorithm());
169		}
170		return(0);
171	} else {
172		return(1);
173	}
174}
175
176/* Get the next entry in the One-time Password database.
177 * Return codes:
178 * -1: error in opening database
179 *  0: next entry found and stored in mp
180 *  1: no more entries, file R/W pointer positioned at EOF
181 */
182int
183skeygetnext(mp)
184	struct skey *mp;
185{
186	long recstart = 0;
187	char *cp;
188	struct stat statbuf;
189
190	/* Open _PATH_SKEYKEYS if it exists, else return an error */
191	if (mp->keyfile == NULL) {
192		if (stat(_PATH_SKEYKEYS, &statbuf) == 0 &&
193		    (mp->keyfile = fopen(_PATH_SKEYKEYS, "r+")) != NULL) {
194			if ((statbuf.st_mode & 0007777) != 0600)
195				fchmod(fileno(mp->keyfile), 0600);
196		} else {
197			return(-1);
198		}
199	}
200
201	/* Look up next user in database */
202	while (!feof(mp->keyfile)) {
203		recstart = ftell(mp->keyfile);
204		mp->recstart = recstart;
205		if (fgets(mp->buf, sizeof(mp->buf), mp->keyfile) != mp->buf)
206			break;
207		rip(mp->buf);
208		if (mp->buf[0] == '#')
209			continue;	/* Comment */
210		if ((mp->logname = strtok(mp->buf, " \t")) == NULL)
211			continue;
212		if ((cp = strtok(NULL, " \t")) == NULL)
213			continue;
214		/* Save hash type if specified, else use md4 */
215		if (isalpha(*cp)) {
216			if ((cp = strtok(NULL, " \t")) == NULL)
217				continue;
218		}
219		mp->n = atoi(cp);
220		if ((mp->seed = strtok(NULL, " \t")) == NULL)
221			continue;
222		if ((mp->val = strtok(NULL, " \t")) == NULL)
223			continue;
224		/* Got a real entry */
225		break;
226	}
227	return(feof(mp->keyfile));
228}
229
230/* Verify response to a s/key challenge.
231 *
232 * Return codes:
233 * -1: Error of some sort; database unchanged
234 *  0:  Verify successful, database updated
235 *  1:  Verify failed, database unchanged
236 *
237 * The database file is always closed by this call.
238 */
239int
240skeyverify(mp, response)
241	struct skey *mp;
242	char *response;
243{
244	char key[SKEY_BINKEY_SIZE];
245	char fkey[SKEY_BINKEY_SIZE];
246	char filekey[SKEY_BINKEY_SIZE];
247	time_t now;
248	struct tm *tm;
249	char tbuf[27];
250	char *cp;
251	int i, rval;
252
253	time(&now);
254	tm = localtime(&now);
255	(void)strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm);
256
257	if (response == NULL) {
258		(void)fclose(mp->keyfile);
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		(void)fclose(mp->keyfile);
267		return(-1);
268	}
269
270	/* Compute fkey = f(key) */
271	(void)memcpy(fkey, key, sizeof(key));
272        (void)fflush(stdout);
273	f(fkey);
274
275	/*
276	 * Obtain an exclusive lock on the key file so the same password
277	 * cannot be used twice to get in to the system.
278	 */
279	for (i = 0; i < 300; i++) {
280		if ((rval = flock(fileno(mp->keyfile), LOCK_EX|LOCK_NB)) == 0 ||
281		    errno != EWOULDBLOCK)
282			break;
283		usleep(100000);			/* Sleep for 0.1 seconds */
284	}
285	if (rval == -1) {			/* Can't get exclusive lock */
286		errno = EAGAIN;
287		return(-1);
288	}
289
290	/* Reread the file record NOW */
291	(void)fseek(mp->keyfile, mp->recstart, SEEK_SET);
292	if (fgets(mp->buf, sizeof(mp->buf), mp->keyfile) != mp->buf) {
293		(void)fclose(mp->keyfile);
294		return(-1);
295	}
296	rip(mp->buf);
297	mp->logname = strtok(mp->buf, " \t");
298	cp = strtok(NULL, " \t") ;
299	if (isalpha(*cp))
300		cp = strtok(NULL, " \t") ;
301	mp->seed = strtok(NULL, " \t");
302	mp->val = strtok(NULL, " \t");
303	/* And convert file value to hex for comparison */
304	atob8(filekey, mp->val);
305
306	/* Do actual comparison */
307	if (memcmp(filekey, fkey, SKEY_BINKEY_SIZE) != 0){
308		/* Wrong response */
309		(void)fclose(mp->keyfile);
310		return(1);
311	}
312
313	/*
314	 * Update key in database by overwriting entire record. Note
315	 * that we must write exactly the same number of bytes as in
316	 * the original record (note fixed width field for N)
317	 */
318	btoa8(mp->val,key);
319	mp->n--;
320	(void)fseek(mp->keyfile, mp->recstart, SEEK_SET);
321	/* Don't save algorithm type for md4 (keep record length same) */
322	if (strcmp(skey_get_algorithm(), "md4") == 0)
323		(void)fprintf(mp->keyfile, "%s %04d %-16s %s %-21s\n",
324			      mp->logname, mp->n, mp->seed, mp->val, tbuf);
325	else
326		(void)fprintf(mp->keyfile, "%s %s %04d %-16s %s %-21s\n",
327			      mp->logname, skey_get_algorithm(), mp->n,
328			      mp->seed, mp->val, tbuf);
329
330	(void)fclose(mp->keyfile);
331
332	return(0);
333}
334
335/*
336 * skey_haskey()
337 *
338 * Returns: 1 user doesnt exist, -1 fle error, 0 user exists.
339 *
340 */
341int
342skey_haskey(username)
343	char *username;
344{
345	struct skey skey;
346
347	return(skeylookup(&skey, username));
348}
349
350/*
351 * skey_keyinfo()
352 *
353 * Returns the current sequence number and
354 * seed for the passed user.
355 *
356 */
357char *
358skey_keyinfo(username)
359	char *username;
360{
361	int i;
362	static char str[SKEY_MAX_CHALLENGE];
363	struct skey skey;
364
365	i = skeychallenge(&skey, username, str);
366	if (i == -1)
367		return(0);
368
369	return(str);
370}
371
372/*
373 * skey_passcheck()
374 *
375 * Check to see if answer is the correct one to the current
376 * challenge.
377 *
378 * Returns: 0 success, -1 failure
379 *
380 */
381int
382skey_passcheck(username, passwd)
383	char *username, *passwd;
384{
385	int i;
386	struct skey skey;
387
388	i = skeylookup(&skey, username);
389	if (i == -1 || i == 1)
390		return(-1);
391
392	if (skeyverify(&skey, passwd) == 0)
393		return(skey.n);
394
395	return(-1);
396}
397
398/*
399 * skey_authenticate()
400 *
401 * Used when calling program will allow input of the user's
402 * response to the challenge.
403 *
404 * Returns: 0 success, -1 failure
405 *
406 */
407int
408skey_authenticate(username)
409	char *username;
410{
411	int i;
412	char pbuf[SKEY_MAX_PW_LEN+1], skeyprompt[SKEY_MAX_CHALLENGE+1];
413	struct skey skey;
414
415	/* Attempt an S/Key challenge */
416	i = skeychallenge(&skey, username, skeyprompt);
417
418	/* Cons up a fake prompt if no entry in keys file */
419	if (i != 0) {
420		char *p, *u;
421
422		/*
423		 * Base first 4 chars of seed on hostname.
424		 * Add some filler for short hostnames if necessary.
425		 */
426		if (gethostname(pbuf, sizeof(pbuf)) == -1)
427			*(p = pbuf) = '.';
428		else
429			for (p = pbuf; *p && isalnum(*p); p++)
430				if (isalpha(*p) && isupper(*p))
431					*p = tolower(*p);
432		if (*p && pbuf - p < 4)
433			(void)strncpy(p, "asjd", 4 - (pbuf - p));
434		p = &pbuf[4];
435		*p = '\0';
436
437		/* Base last 8 chars of seed on username */
438		u = username;
439		i = 8;
440		do {
441			if (*u == 0) {
442				/* Pad remainder with zeros */
443				while (--i >= 0)
444					*p++ = '0';
445				break;
446			}
447
448			*p++ = (*u++ % 10) + '0';
449		} while (--i != 0);
450		pbuf[12] = '\0';
451
452		(void)sprintf(skeyprompt, "otp-%.*s %d %.*s",
453			      SKEY_MAX_HASHNAME_LEN, skey_get_algorithm(),
454			      99, SKEY_MAX_SEED_LEN, pbuf);
455	}
456
457	(void)fprintf(stderr, "%s\n", skeyprompt);
458	(void)fflush(stderr);
459
460	(void)fputs("Response: ", stderr);
461	readskey(pbuf, sizeof(pbuf));
462
463	/* Is it a valid response? */
464	if (i == 0 && skeyverify(&skey, pbuf) == 0) {
465		if (skey.n < 5) {
466			(void)fprintf(stderr,
467			    "\nWarning! Key initialization needed soon.  (%d logins left)\n",
468			    skey.n);
469		}
470		return(0);
471	}
472	return(-1);
473}
474
475/* Comment out user's entry in the s/key database
476 *
477 * Return codes:
478 * -1: Write error; database unchanged
479 *  0:  Database updated
480 *
481 * The database file is always closed by this call.
482 */
483int
484skeyzero(mp, response)
485	struct skey *mp;
486	char *response;
487{
488	/*
489	 * Seek to the right place and write comment character
490	 * which effectively zero's out the entry.
491	 */
492	(void)fseek(mp->keyfile, mp->recstart, SEEK_SET);
493	if (fputc('#', mp->keyfile) == EOF) {
494		fclose(mp->keyfile);
495		return(-1);
496	}
497
498	(void)fclose(mp->keyfile);
499
500	return(0);
501}
502