1 /* $OpenBSD: skeyinit.c,v 1.29 2001/06/23 22:29:14 millert Exp $ */ 2 3 /* OpenBSD S/Key (skeyinit.c) 4 * 5 * Authors: 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> 10 * Todd C. Miller <Todd.Miller@courtesan.com> 11 * 12 * S/Key initialization and seed update 13 */ 14 15 #include <sys/param.h> 16 #include <sys/file.h> 17 #include <sys/time.h> 18 #include <sys/resource.h> 19 20 #include <err.h> 21 #include <errno.h> 22 #include <ctype.h> 23 #include <pwd.h> 24 #include <readpassphrase.h> 25 #include <stdio.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <syslog.h> 29 #include <time.h> 30 #include <unistd.h> 31 #include <utmp.h> 32 33 #include <skey.h> 34 35 #ifndef SKEY_NAMELEN 36 #define SKEY_NAMELEN 4 37 #endif 38 39 void lockeof __P((struct skey *, char *)); 40 void usage __P((char *)); 41 void secure_mode __P((int *, char *, char *, char *, char *, size_t)); 42 void normal_mode __P((char *, int, char *, char *, char *)); 43 void timedout __P((int)); 44 45 int 46 main(argc, argv) 47 int argc; 48 char *argv[]; 49 { 50 int rval, i, l, n=0, defaultsetup=1, zerokey=0, hexmode=0; 51 int oldmd4=0; 52 time_t now; 53 size_t seedlen; 54 char hostname[MAXHOSTNAMELEN]; 55 char passwd[SKEY_MAX_PW_LEN+2]; 56 char seed[SKEY_MAX_SEED_LEN+2], defaultseed[SKEY_MAX_SEED_LEN+1]; 57 char tbuf[27], buf[256], key[SKEY_BINKEY_SIZE]; 58 char lastc, me[UT_NAMESIZE+1], *salt, *p, *ht=NULL; 59 struct skey skey; 60 struct passwd *pp; 61 struct tm *tm; 62 63 if (geteuid() != 0) 64 errx(1, "must be setuid root."); 65 66 /* Build up a default seed based on the hostname and time */ 67 if (gethostname(hostname, sizeof(hostname)) < 0) 68 err(1, "gethostname"); 69 for (i = 0, p = defaultseed; hostname[i] && i < SKEY_NAMELEN; i++) { 70 if (isalpha(hostname[i])) { 71 if (isupper(hostname[i])) 72 hostname[i] = tolower(hostname[i]); 73 *p++ = hostname[i]; 74 } else if (isdigit(hostname[i])) 75 *p++ = hostname[i]; 76 } 77 *p = '\0'; 78 (void)time(&now); 79 (void)sprintf(tbuf, "%05ld", (long) (now % 100000)); 80 (void)strncat(defaultseed, tbuf, sizeof(defaultseed) - 5); 81 82 if ((pp = getpwuid(getuid())) == NULL) 83 err(1, "no user with uid %d", getuid()); 84 (void)strcpy(me, pp->pw_name); 85 86 if ((pp = getpwnam(me)) == NULL) 87 err(1, "Who are you?"); 88 salt = pp->pw_passwd; 89 90 for (i = 1; i < argc && argv[i][0] == '-' && strcmp(argv[i], "--");) { 91 if (argv[i][2] == '\0') { 92 /* Single character switch */ 93 switch (argv[i][1]) { 94 case 's': 95 defaultsetup = 0; 96 break; 97 case 'x': 98 hexmode = 1; 99 break; 100 case 'z': 101 zerokey = 1; 102 break; 103 case 'n': 104 if (argv[++i] == NULL || argv[i][0] == '\0') 105 usage(argv[0]); 106 if ((n = atoi(argv[i])) < 1 || n >= SKEY_MAX_SEQ) 107 errx(1, "count must be > 0 and < %d", 108 SKEY_MAX_SEQ); 109 break; 110 default: 111 usage(argv[0]); 112 } 113 } else { 114 /* Multi character switches are hash types */ 115 if ((ht = skey_set_algorithm(&argv[i][1])) == NULL) { 116 warnx("Unknown hash algorithm %s", &argv[i][1]); 117 usage(argv[0]); 118 } 119 } 120 i++; 121 } 122 123 /* check for optional user string */ 124 if (argc - i > 1) { 125 usage(argv[0]); 126 } else if (argv[i]) { 127 if ((pp = getpwnam(argv[i])) == NULL) { 128 if (getuid() == 0) { 129 static struct passwd _pp; 130 131 _pp.pw_name = argv[i]; 132 pp = &_pp; 133 warnx("Warning, user unknown: %s", argv[i]); 134 } else { 135 errx(1, "User unknown: %s", argv[i]); 136 } 137 } else if (strcmp(pp->pw_name, me) != 0) { 138 if (getuid() != 0) { 139 /* Only root can change other's passwds */ 140 errx(1, "Permission denied."); 141 } 142 } 143 } 144 145 if (defaultsetup) 146 fputs("Reminder - Only use this method if you are directly connected\n or have an encrypted channel. If you are using telnet\n or rlogin, hit return now and use skeyinit -s.\n", stderr); 147 148 if (getuid() != 0) { 149 /* XXX - use BSD auth */ 150 passwd[0] = '\0'; 151 if (!defaultsetup && skeychallenge(&skey, me, buf) == 0) { 152 printf("Enter S/Key password below or hit return twice " 153 "to enter standard password.\n%s\n", buf); 154 fflush(stdout); 155 if (!readpassphrase("S/Key Password: ", passwd, 156 sizeof(passwd), 0) || passwd[0] == '\0') { 157 readpassphrase("S/Key Password: [echo on] ", 158 passwd, sizeof(passwd), RPP_ECHO_ON); 159 } 160 } 161 if (passwd[0]) { 162 if (skeyverify(&skey, passwd) != 0) 163 errx(1, "Password incorrect."); 164 } else { 165 fflush(stdout); 166 readpassphrase("Password: ", passwd, sizeof(passwd), 0); 167 if (strcmp(crypt(passwd, salt), pp->pw_passwd)) { 168 if (passwd[0]) 169 warnx("Password incorrect."); 170 exit(1); 171 } 172 } 173 } 174 175 /* 176 * Lookup and lock the record we are about to modify. 177 * If this is a new entry this will prevent other users 178 * from appending new entries (and clobbering ours). 179 */ 180 rval = skeylookup(&skey, pp->pw_name); 181 switch (rval) { 182 case -1: 183 if (errno == ENOENT) 184 errx(1, "S/Key disabled"); 185 else 186 err(1, "cannot open database"); 187 break; 188 case 0: 189 /* comment out user if asked to */ 190 if (zerokey) 191 exit(skeyzero(&skey)); 192 193 (void)printf("[Updating %s with %s]\n", pp->pw_name, 194 ht ? ht : skey_get_algorithm()); 195 (void)printf("Old seed: [%s] %s\n", 196 skey_get_algorithm(), skey.seed); 197 198 /* 199 * Sanity check old seed. 200 */ 201 l = strlen(skey.seed); 202 for (p = skey.seed; *p; p++) { 203 if (isalpha(*p)) { 204 if (isupper(*p)) 205 *p = tolower(*p); 206 } else if (!isdigit(*p)) { 207 memmove(p, p + 1, l - (p - skey.seed)); 208 l--; 209 } 210 } 211 212 /* If the seed ends in 0-8 just add one. */ 213 if (l > 0) { 214 lastc = skey.seed[l - 1]; 215 if (isdigit(lastc) && lastc != '9') { 216 (void)strcpy(defaultseed, skey.seed); 217 defaultseed[l - 1] = lastc + 1; 218 } 219 if (isdigit(lastc) && lastc == '9' && l < 16) { 220 (void)strcpy(defaultseed, skey.seed); 221 defaultseed[l - 1] = '0'; 222 defaultseed[l] = '0'; 223 defaultseed[l + 1] = '\0'; 224 } 225 } 226 break; 227 case 1: 228 if (zerokey) 229 errx(1, "You have no entry to zero."); 230 (void)printf("[Adding %s with %s]\n", pp->pw_name, 231 ht ? ht : skey_get_algorithm()); 232 lockeof(&skey, pp->pw_name); 233 break; 234 } 235 if (n == 0) 236 n = 99; 237 238 /* Do we have an old-style md4 entry? */ 239 if (rval == 0 && strcmp("md4", skey_get_algorithm()) == 0 && 240 strcmp("md4", skey.logname + strlen(skey.logname) + 1) != 0) 241 oldmd4 = 1; 242 243 /* Set hash type if asked to */ 244 if (ht && strcmp(ht, skey_get_algorithm()) != 0) 245 skey_set_algorithm(ht); 246 247 alarm(180); 248 if (!defaultsetup) 249 secure_mode(&n, key, seed, defaultseed, buf, sizeof(buf)); 250 else 251 normal_mode(pp->pw_name, n, key, seed, defaultseed); 252 alarm(0); 253 254 (void)time(&now); 255 tm = localtime(&now); 256 (void)strftime(tbuf, sizeof(tbuf), " %b %d,%Y %T", tm); 257 258 /* If this is an exiting entry, compute the line length and seed pad */ 259 seedlen = SKEY_MAX_SEED_LEN; 260 if (rval == 0) { 261 int nlen; 262 263 nlen = strlen(pp->pw_name) + 1 + strlen(skey_get_algorithm()) + 264 1 + 4 + 1 + strlen(seed) + 1 + 16 + 1 + strlen(tbuf) + 1; 265 266 /* 267 * If there was no hash type (md4) add one unless we 268 * are short on space. 269 */ 270 if (oldmd4) { 271 if (nlen > skey.len) 272 nlen -= 4; 273 else 274 oldmd4 = 0; 275 } 276 277 /* If new entry is longer than the old, comment out the old. */ 278 if (nlen > skey.len) { 279 (void)skeyzero(&skey); 280 /* Re-open keys file and seek to the end */ 281 if (skeylookup(&skey, pp->pw_name) == -1) 282 err(1, "cannot reopen database"); 283 lockeof(&skey, pp->pw_name); 284 } else { 285 /* Compute how much to space-pad the seed */ 286 seedlen = strlen(seed) + (skey.len - nlen); 287 } 288 } 289 290 if ((skey.val = (char *)malloc(16 + 1)) == NULL) 291 err(1, "Can't allocate memory"); 292 btoa8(skey.val, key); 293 294 /* Don't save algorithm type for md4 (maintain record length) */ 295 /* XXX - should check return values of fprintf + fclose */ 296 if (oldmd4) 297 (void)fprintf(skey.keyfile, "%s %04d %-* %s %-21s\n", 298 pp->pw_name, n, seedlen, seed, skey.val, tbuf); 299 else 300 (void)fprintf(skey.keyfile, "%s %s %04d %-*s %s %-21s\n", 301 pp->pw_name, skey_get_algorithm(), n, seedlen, seed, 302 skey.val, tbuf); 303 (void)fclose(skey.keyfile); 304 305 (void)printf("\nID %s skey is otp-%s %d %s\n", pp->pw_name, 306 skey_get_algorithm(), n, seed); 307 (void)printf("Next login password: %s\n\n", 308 hexmode ? put8(buf, key) : btoe(buf, key)); 309 exit(0); 310 } 311 312 void 313 lockeof(mp, user) 314 struct skey *mp; 315 char *user; 316 { 317 struct flock fl; 318 319 fseek(mp->keyfile, 0, SEEK_END); 320 dolock: 321 fl.l_start = ftell(mp->keyfile); 322 fl.l_len = mp->len; 323 fl.l_pid = getpid(); 324 fl.l_type = F_WRLCK; 325 fl.l_whence = SEEK_SET; 326 327 if (fcntl(fileno(mp->keyfile), F_SETLKW, &fl) == -1) 328 err(1, "Can't lock database"); 329 330 /* Make sure we are still at the end. */ 331 fseek(mp->keyfile, 0, SEEK_END); 332 if (fl.l_start == ftell(mp->keyfile)) 333 return; /* still at EOF */ 334 335 fclose(mp->keyfile); 336 if (skeylookup(mp, user) != 1) 337 errx(1, "user %s already added", user); 338 goto dolock; 339 } 340 341 void 342 secure_mode(count, key, seed, defaultseed, buf, bufsiz) 343 int *count; 344 char *key; 345 char *seed; 346 char *defaultseed; 347 char *buf; 348 size_t bufsiz; 349 { 350 int i, n; 351 char *p; 352 353 (void)puts("You need the 6 words generated from the \"skey\" command."); 354 for (i = 0; ; i++) { 355 if (i >= 2) 356 exit(1); 357 358 (void)printf("Enter sequence count from 1 to %d: ", 359 SKEY_MAX_SEQ); 360 (void)fgets(buf, bufsiz, stdin); 361 clearerr(stdin); 362 n = atoi(buf); 363 if (n > 0 && n < SKEY_MAX_SEQ) 364 break; /* Valid range */ 365 (void)fprintf(stderr, "ERROR: Count must be between 1 and %d\n", 366 SKEY_MAX_SEQ); 367 } 368 369 for (i = 0; ; i++) { 370 if (i >= 2) 371 exit(1); 372 373 (void)printf("Enter new seed [default %s]: ", 374 defaultseed); 375 (void)fgets(seed, SKEY_MAX_SEED_LEN+2, stdin); /* XXX */ 376 clearerr(stdin); 377 rip(seed); 378 if (strlen(seed) > SKEY_MAX_SEED_LEN) { 379 (void)fprintf(stderr, "ERROR: Seed must be between 1 " 380 "and %d characters in length\n", SKEY_MAX_SEED_LEN); 381 continue; 382 } 383 if (seed[0] == '\0') 384 (void)strcpy(seed, defaultseed); 385 for (p = seed; *p; p++) { 386 if (isspace(*p)) { 387 (void)fputs("ERROR: Seed must not contain " 388 "any spaces\n", stderr); 389 break; 390 } else if (isalpha(*p)) { 391 if (isupper(*p)) 392 *p = tolower(*p); 393 } else if (!isdigit(*p)) { 394 (void)fputs("ERROR: Seed must be purely " 395 "alphanumeric\n", stderr); 396 break; 397 } 398 } 399 if (*p == '\0') 400 break; /* Valid seed */ 401 } 402 403 for (i = 0; ; i++) { 404 if (i >= 2) 405 exit(1); 406 407 (void)printf("otp-%s %d %s\nS/Key access password: ", 408 skey_get_algorithm(), n, seed); 409 (void)fgets(buf, bufsiz, stdin); 410 clearerr(stdin); 411 rip(buf); 412 backspace(buf); 413 414 if (buf[0] == '?') { 415 (void)puts("Enter 6 words from secure S/Key calculation."); 416 continue; 417 } else if (buf[0] == '\0') 418 exit(1); 419 420 if (etob(key, buf) == 1 || atob8(key, buf) == 0) 421 break; /* Valid format */ 422 (void)fputs("ERROR: Invalid format - try again with the 6 words.\n", 423 stderr); 424 } 425 *count= n; 426 } 427 428 void 429 normal_mode(username, n, key, seed, defaultseed) 430 char *username; 431 int n; 432 char *key; 433 char *seed; 434 char *defaultseed; 435 { 436 int i, nn; 437 char passwd[SKEY_MAX_PW_LEN+2], passwd2[SKEY_MAX_PW_LEN+2]; 438 439 /* Get user's secret passphrase */ 440 for (i = 0; ; i++) { 441 if (i > 2) 442 exit(1); 443 444 if (readpassphrase("Enter secret passphrase: ", passwd, 445 sizeof(passwd), 0) == NULL || passwd[0] == '\0') 446 exit(1); 447 448 if (strlen(passwd) < SKEY_MIN_PW_LEN) { 449 (void)fprintf(stderr, 450 "ERROR: Your passphrase must be at least %d " 451 "characters long.\n", SKEY_MIN_PW_LEN); 452 continue; 453 } else if (strcmp(passwd, username) == 0) { 454 (void)fputs("ERROR: Your passphrase may not be the " 455 "same as your user name.\n", stderr); 456 continue; 457 } else if (strspn(passwd, "abcdefghijklmnopqrstuvwxyz") == 458 strlen(passwd)) { 459 (void)fputs("ERROR: Your passphrase must contain more " 460 "than just lower case letters.\nWhitespace, " 461 "numbers, and puctuation are suggested.\n", stderr); 462 continue; 463 } else if (strlen(passwd) > 63) { 464 (void)fprintf(stderr, "WARNING: Your passphrase is " 465 "longer than the recommended maximum length of 63\n"); 466 } 467 /* XXX - should check for passphrase that is really too long */ 468 469 if (readpassphrase("Again secret passphrase: ", passwd2, 470 sizeof(passwd2), 0) && strcmp(passwd, passwd2) == 0) 471 break; 472 473 (void)fputs("Passphrases do not match.\n", stderr); 474 } 475 476 /* Crunch seed and passphrase into starting key */ 477 (void)strcpy(seed, defaultseed); 478 if (keycrunch(key, seed, passwd) != 0) 479 err(2, "key crunch failed"); 480 481 nn = n; 482 while (nn-- != 0) 483 f(key); 484 } 485 486 #define TIMEOUT_MSG "Timed out waiting for input.\n" 487 void 488 timedout(signo) 489 int signo; 490 { 491 492 write(STDERR_FILENO, TIMEOUT_MSG, sizeof(TIMEOUT_MSG) - 1); 493 _exit(1); 494 } 495 496 void 497 usage(s) 498 char *s; 499 { 500 (void)fprintf(stderr, 501 "Usage: %s [-s] [-x] [-z] [-n count] [-md4|-md5|-sha1|-rmd160] [user]\n", s); 502 exit(1); 503 } 504