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