skeylogin.c revision 1.3
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 * S/KEY verification check, lookups, and authentication.
10 *
11 * $Id: skeylogin.c,v 1.3 1996/09/27 15:38:59 millert Exp $
12 */
13
14#include <sys/param.h>
15#ifdef	QUOTA
16#include <sys/quota.h>
17#endif
18#include <sys/stat.h>
19#include <sys/time.h>
20#include <sys/timeb.h>
21#include <sys/resource.h>
22
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <sys/types.h>
27#include <sys/stat.h>
28#include <time.h>
29#include <errno.h>
30
31#include "skey.h"
32
33#define	_PATH_KEYFILE	"/etc/skeykeys"
34
35char *skipspace __ARGS((char *));
36int skeylookup __ARGS((struct skey *, char *));
37
38/* Issue a skey challenge for user 'name'. If successful,
39 * fill in the caller's skey structure and return 0. If unsuccessful
40 * (e.g., if name is unknown) return -1.
41 *
42 * The file read/write pointer is left at the start of the
43 * record.
44 */
45int
46getskeyprompt(mp, name, prompt)
47	struct skey *mp;
48	char *name;
49	char *prompt;
50{
51	int rval;
52
53	sevenbit(name);
54	rval = skeylookup(mp, name);
55	(void)strcpy(prompt, "s/key MD0 55 latour1\n");
56	switch (rval) {
57	case -1:	/* File error */
58		return -1;
59	case 0:		/* Lookup succeeded, return challenge */
60		(void)sprintf(prompt, "s/key MD%d %d %s\n", skey_get_MDX(),
61			      mp->n - 1, mp->seed);
62		return 0;
63	case 1:		/* User not found */
64		(void)fclose(mp->keyfile);
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 */
77int
78skeychallenge(mp,name, ss)
79	struct skey *mp;
80	char *name;
81	char *ss;
82{
83	int rval;
84
85	rval = skeylookup(mp,name);
86	switch(rval){
87	case -1:	/* File error */
88		return -1;
89	case 0:		/* Lookup succeeded, issue challenge */
90		(void)sprintf(ss, "s/key MD%d %d %s", skey_get_MDX(),
91			      mp->n - 1, mp->seed);
92		return 0;
93	case 1:		/* User not found */
94		(void)fclose(mp->keyfile);
95		return -1;
96	}
97	return -1;	/* Can't happen */
98}
99
100/* Find an entry in the One-time Password database.
101 * Return codes:
102 * -1: error in opening database
103 *  0: entry found, file R/W pointer positioned at beginning of record
104 *  1: entry not found, file R/W pointer positioned at EOF
105 */
106int
107skeylookup(mp,name)
108	struct skey *mp;
109	char *name;
110{
111	int found, len;
112	long recstart = 0;
113	char *cp;
114	struct stat statbuf;
115
116	/* See if _PATH_KEYFILE exists, and create it if not */
117	if (stat(_PATH_KEYFILE, &statbuf) == -1 && errno == ENOENT) {
118		mp->keyfile = fopen(_PATH_KEYFILE, "w+");
119		if (mp->keyfile)
120			chmod(_PATH_KEYFILE, 0644);
121	} else {
122		/* Otherwise open normally for update */
123		mp->keyfile = fopen(_PATH_KEYFILE, "r+");
124	}
125	if (mp->keyfile == NULL)
126		return -1;
127
128	/* Look up user name in database */
129	len = strlen(name);
130	/* XXX - do we really want to limit it to 8 char usernames? */
131	if (len > 8)
132		len = 8;		/*  Added 8/2/91  -  nmh */
133	found = 0;
134	while (!feof(mp->keyfile)) {
135		recstart = ftell(mp->keyfile);
136		mp->recstart = recstart;
137		if (fgets(mp->buf, sizeof(mp->buf), mp->keyfile) != mp->buf) {
138			break;
139		}
140		rip(mp->buf);
141		if (mp->buf[0] == '#')
142			continue;	/* Comment */
143		if ((mp->logname = strtok(mp->buf, " \t")) == NULL)
144			continue;
145		if ((cp = strtok(NULL, " \t")) == NULL)
146			continue;
147		/* Set MDX if specified, else use MD4 */
148		if (cp[0] == 'M' && cp[1] == 'D') {
149			skey_set_MDX(atoi(&cp[2]));
150			if ((cp = strtok(NULL, " \t")) == NULL)
151				continue;
152		} else {
153			skey_set_MDX(4);
154		}
155		mp->n = atoi(cp);
156		if ((mp->seed = strtok(NULL, " \t")) == NULL)
157			continue;
158		if ((mp->val = strtok(NULL, " \t")) == NULL)
159			continue;
160		if (strlen(mp->logname) == len &&
161		    strncmp(mp->logname, name, len) == 0) {
162			found = 1;
163			break;
164		}
165	}
166	if (found) {
167		(void)fseek(mp->keyfile, recstart, SEEK_SET);
168		return 0;
169	} else
170		return 1;
171}
172
173/* Verify response to a s/key challenge.
174 *
175 * Return codes:
176 * -1: Error of some sort; database unchanged
177 *  0:  Verify successful, database updated
178 *  1:  Verify failed, database unchanged
179 *
180 * The database file is always closed by this call.
181 */
182int
183skeyverify(mp,response)
184	struct skey *mp;
185	char *response;
186{
187	char key[8];
188	char fkey[8];
189	char filekey[8];
190	time_t now;
191	struct tm *tm;
192	char tbuf[27];
193	char *cp;
194
195	time(&now);
196	tm = localtime(&now);
197	(void)strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm);
198
199	if (response == NULL) {
200		(void)fclose(mp->keyfile);
201		return -1;
202	}
203	rip(response);
204
205	/* Convert response to binary */
206	if (etob(key, response) != 1 && atob8(key, response) != 0) {
207		/* Neither english words or ascii hex */
208		(void)fclose(mp->keyfile);
209		return -1;
210	}
211
212	/* Compute fkey = f(key) */
213	(void)memcpy(fkey,key,sizeof(key));
214        (void)fflush(stdout);
215	f(fkey);
216
217	/*
218	 * in order to make the window of update as short as possible
219	 * we must do the comparison here and if OK write it back
220	 * other wise the same password can be used twice to get in
221	 * to the system
222	 */
223	(void)setpriority(PRIO_PROCESS, 0, -4);
224
225	/* reread the file record NOW */
226	(void)fseek(mp->keyfile, mp->recstart, SEEK_SET);
227	if (fgets(mp->buf, sizeof(mp->buf), mp->keyfile) != mp->buf) {
228		(void)setpriority(PRIO_PROCESS, 0, 0);
229		(void)fclose(mp->keyfile);
230		return -1;
231	}
232	rip(mp->buf);
233	mp->logname = strtok(mp->buf," \t");
234	cp = strtok(NULL," \t") ;
235	cp = strtok(NULL," \t") ;
236	mp->seed = strtok(NULL," \t");
237	mp->val = strtok(NULL," \t");
238	/* And convert file value to hex for comparison */
239	atob8(filekey,mp->val);
240
241	/* Do actual comparison */
242	if (memcmp(filekey,fkey,8) != 0){
243		/* Wrong response */
244		(void)setpriority(PRIO_PROCESS, 0, 0);
245		(void)fclose(mp->keyfile);
246		return 1;
247	}
248
249	/*
250	 * Update key in database by overwriting entire record. Note
251	 * that we must write exactly the same number of bytes as in
252	 * the original record (note fixed width field for N)
253	 */
254	btoa8(mp->val,key);
255	mp->n--;
256	(void)fseek(mp->keyfile, mp->recstart, SEEK_SET);
257	(void)fprintf(mp->keyfile, "%s MD%d %04d %-16s %s %-21s\n",
258	    mp->logname, skey_get_MDX(), mp->n, mp->seed, mp->val, tbuf);
259
260	(void)fclose(mp->keyfile);
261
262	(void)setpriority(PRIO_PROCESS, 0, 0);
263	return 0;
264}
265
266/*
267 * skey_haskey()
268 *
269 * Returns: 1 user doesnt exist, -1 fle error, 0 user exists.
270 *
271 */
272int
273skey_haskey(username)
274	char *username;
275{
276	struct skey skey;
277
278	return(skeylookup(&skey, username));
279}
280
281/*
282 * skey_keyinfo()
283 *
284 * Returns the current sequence number and
285 * seed for the passed user.
286 *
287 */
288char *
289skey_keyinfo(username)
290	char *username;
291{
292	int i;
293	static char str[50];
294	struct skey skey;
295
296	i = skeychallenge(&skey, username, str);
297	if (i == -1)
298		return 0;
299
300	return str;
301}
302
303/*
304 * skey_passcheck()
305 *
306 * Check to see if answer is the correct one to the current
307 * challenge.
308 *
309 * Returns: 0 success, -1 failure
310 *
311 */
312int
313skey_passcheck(username, passwd)
314	char *username, *passwd;
315{
316	int i;
317	struct skey skey;
318
319	i = skeylookup(&skey, username);
320	if (i == -1 || i == 1)
321		return -1;
322
323	if (skeyverify(&skey, passwd) == 0)
324		return skey.n;
325
326	return -1;
327}
328
329/*
330 * skey_authenticate()
331 *
332 * Used when calling program will allow input of the user's
333 * response to the challenge.
334 *
335 * Returns: 0 success, -1 failure
336 *
337 */
338int
339skey_authenticate(username)
340	char *username;
341{
342	int i;
343	char pbuf[256], skeyprompt[50];
344	struct skey skey;
345
346	/* Attempt an S/Key challenge */
347	i = skeychallenge(&skey, username, skeyprompt);
348
349	if (i == -2)
350		return 0;
351
352	(void)fprintf(stderr, "[%s]\n", skeyprompt);
353	(void)fflush(stderr);
354
355	(void)fputs("Response: ", stderr);
356	readskey(pbuf, sizeof(pbuf));
357	rip(pbuf);
358
359	/* Is it a valid response? */
360	if (i == 0 && skeyverify(&skey, pbuf) == 0) {
361		if (skey.n < 5) {
362			(void)fprintf(stderr,
363			    "\nWarning! Key initialization needed soon.  (%d logins left)\n",
364			    skey.n);
365		}
366		return 0;
367	}
368	return -1;
369}
370
371/* Comment out user's entry in the s/key database
372 *
373 * Return codes:
374 * -1: Write error; database unchanged
375 *  0:  Database updated
376 *
377 * The database file is always closed by this call.
378 */
379int
380skeyzero(mp, response)
381	struct skey *mp;
382	char *response;
383{
384	/*
385	 * Seek to the right place and write comment character
386	 * which effectively zero's out the entry.
387	 */
388	(void)fseek(mp->keyfile, mp->recstart, SEEK_SET);
389	if (fputc('#', mp->keyfile) == EOF) {
390		fclose(mp->keyfile);
391		return -1;
392	}
393
394	(void)fclose(mp->keyfile);
395
396	return 0;
397}
398