1 /* $NetBSD: skeylogin.c,v 1.18 2003/03/09 00:46:07 lukem Exp $ */
3 /* S/KEY v1.1b (skeylogin.c)
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>
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>
24 #include <sys/resource.h>
25 #include <sys/types.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
49 int getskeyprompt(struct skey
*mp
, char *name
, char *prompt
)
54 rval
= skeylookup(mp
, name
);
58 case -1: /* File error */
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
);
65 case 1: /* User not found */
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
80 int skeychallenge(struct skey
*mp
, const char *name
, char *ss
, size_t sslen
)
84 rval
= skeylookup(mp
, name
);
88 case -1: /* File error */
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
);
95 case 1: /* User not found */
100 return -1; /* Can't happen */
103 static FILE *openSkey(void)
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);
120 /* Find an entry in the One-time Password database.
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
)
130 const char *ht
= NULL
;
133 if(!(mp
->keyfile
= openSkey()))
136 /* Look up user name in database */
137 while (!feof(mp
->keyfile
)) {
140 recstart
= ftell(mp
->keyfile
);
141 mp
->recstart
= recstart
;
142 if (fgets(mp
->buf
, sizeof(mp
->buf
), mp
->keyfile
) != mp
->buf
)
146 if (mp
->buf
[0] == '#')
147 continue; /* Comment */
148 if ((mp
->logname
= strtok_r(mp
->buf
, " \t", &last
)) == NULL
)
150 if ((cp
= strtok_r(NULL
, " \t", &last
)) == NULL
)
152 /* Save hash type if specified, else use md4 */
153 if (isalpha((u_char
) *cp
)) {
155 if ((cp
= strtok_r(NULL
, " \t", &last
)) == NULL
)
161 if ((mp
->seed
= strtok_r(NULL
, " \t", &last
)) == NULL
)
163 if ((mp
->val
= strtok_r(NULL
, " \t", &last
)) == NULL
)
165 if (strcmp(mp
->logname
, name
) == 0) {
171 fseek(mp
->keyfile
, recstart
, SEEK_SET
);
173 if (ht
&& skey_set_algorithm(ht
) == NULL
) {
174 warnx("Unknown hash algorithm %s, using %s", ht
,
175 skey_get_algorithm());
183 /* Get the next entry in the One-time Password database.
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
)
194 /* Open _PATH_SKEYKEYS if it exists, else return an error */
195 if (mp
->keyfile
== NULL
) {
196 if(!(mp
->keyfile
= openSkey()))
200 /* Look up next user in database */
201 while (!feof(mp
->keyfile
)) {
204 recstart
= ftell(mp
->keyfile
);
205 mp
->recstart
= recstart
;
206 if (fgets(mp
->buf
, sizeof(mp
->buf
), mp
->keyfile
) != mp
->buf
)
209 if (mp
->buf
[0] == '#')
210 continue; /* Comment */
211 if ((mp
->logname
= strtok_r(mp
->buf
, " \t", &last
)) == NULL
)
213 if ((cp
= strtok_r(NULL
, " \t", &last
)) == NULL
)
215 /* Save hash type if specified, else use md4 */
216 if (isalpha((u_char
) *cp
)) {
217 if ((cp
= strtok_r(NULL
, " \t", &last
)) == NULL
)
221 if ((mp
->seed
= strtok_r(NULL
, " \t", &last
)) == NULL
)
223 if ((mp
->val
= strtok_r(NULL
, " \t", &last
)) == NULL
)
225 /* Got a real entry */
228 return(feof(mp
->keyfile
));
231 /* Verify response to a s/key challenge.
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
];
253 tm
= localtime(&now
);
254 strftime(tbuf
, sizeof(tbuf
), " %b %d,%Y %T", tm
);
256 if (response
== NULL
) {
263 /* Convert response to binary */
264 if (etob(key
, response
) != 1 && atob8(key
, response
) != 0) {
265 /* Neither english words or ascii hex */
271 /* Compute fkey = f(key) */
272 memcpy(fkey
, key
, sizeof(key
));
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
)
285 usleep(100000); /* Sleep for 0.1 seconds */
287 if (rval
== -1) { /* Can't get exclusive lock */
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
) {
301 if ((mp
->logname
= strtok_r(mp
->buf
, " \t", &last
)) == NULL
)
303 if ((cp
= strtok_r(NULL
, " \t", &last
)) == NULL
)
305 if (isalpha((u_char
) *cp
))
306 if ((cp
= strtok_r(NULL
, " \t", &last
)) == NULL
)
308 if ((mp
->seed
= strtok_r(NULL
, " \t", &last
)) == NULL
)
310 if ((mp
->val
= strtok_r(NULL
, " \t", &last
)) == NULL
)
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) {
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)
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
);
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
);
351 /* Returns: 1 user doesnt exist, -1 file error, 0 user exists. */
353 int skey_haskey(const char *username
)
358 i
= skeylookup(&skey
, username
);
360 if (skey
.keyfile
!= NULL
) {
361 fclose(skey
.keyfile
);
368 * Returns the current sequence number and
369 * seed for the passed user.
371 const char *skey_keyinfo(const char *username
)
374 static char str
[SKEY_MAX_CHALLENGE
];
377 i
= skeychallenge(&skey
, username
, str
, sizeof str
);
381 if (skey
.keyfile
!= NULL
) {
382 fclose(skey
.keyfile
);
389 * Check to see if answer is the correct one to the current
392 * Returns: 0 success, -1 failure
395 int skey_passcheck(const char *username
, char *passwd
)
400 i
= skeylookup (&skey
, username
);
401 if (i
== -1 || i
== 1)
404 if (skeyverify (&skey
, passwd
) == 0)
410 #if DO_FAKE_CHALLENGE
411 #define ROUND(x) (((x)[0] << 24) + (((x)[1]) << 16) + (((x)[2]) << 8) + \
417 static u_int32_t
hash_collapse(u_char
*s
)
423 slen
= strlen((char *)s
);
424 if ((slen
% sizeof(u_int32_t
)) == 0)
425 target
= slen
; /* Multiple of 4 */
427 target
= slen
- slen
% sizeof(u_int32_t
);
429 for (i
= 0, len
= 0; len
< target
; len
+= 4)
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
)
446 char pbuf
[SKEY_MAX_PW_LEN
+1], skeyprompt
[SKEY_MAX_CHALLENGE
+1];
448 #if DO_FAKE_CHALLENGE
450 u_char hseed
[SKEY_MAX_SEED_LEN
], flg
= 1, *up
;
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 */
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)
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
));
477 /* Hash the username if possible */
478 if ((up
= SHA1Data(username
, strlen(username
), NULL
)) != NULL
) {
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
) {
497 secretlen
= SKEY_MAX_SEED_LEN
;
499 } else if (!stat(_PATH_MEM
, &sb
) || !stat("/", &sb
)) {
502 secretlen
= strlen(secret
);
509 /* Put that in your pipe and smoke it */
511 /* Hash secret value with username */
513 SHA1Update(&ctx
, secret
, secretlen
);
514 SHA1Update(&ctx
, username
, strlen(username
));
518 memset(secret
, 0, secretlen
);
520 /* Now hash the hash */
522 SHA1Update(&ctx
, up
, strlen(up
));
525 ptr
= hash_collapse(up
+ 4);
527 for (i
= 4; i
< 9; i
++) {
528 pbuf
[i
] = (ptr
% 10) + '0';
533 /* Sequence number */
534 ptr
= ((up
[2] + up
[3]) % 99) + 1;
536 memset(up
, 0, 20); /* SHA1 specific */
539 (void)sprintf(skeyprompt
,
541 SKEY_MAX_HASHNAME_LEN
,
542 skey_get_algorithm(),
543 ptr
, SKEY_MAX_SEED_LEN
,
546 /* Base last 8 chars of seed on username */
552 /* Pad remainder with zeros */
558 *p
++ = (*u
++ % 10) + '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
);
570 fprintf(stderr
, "[%s]\n", skeyprompt
);
573 fputs("Response: ", stderr
);
574 readskey(pbuf
, sizeof(pbuf
));
576 /* Is it a valid response? */
577 if (i
== 0 && skeyverify(&skey
, pbuf
) == 0) {
580 "\nWarning! Key initialization needed soon. (%d logins left)\n",
588 /* Comment out user's entry in the s/key database
591 * -1: Write error; database unchanged
592 * 0: Database updated
594 * The database file is always closed by this call.
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
) {