1*0Sstevel@tonic-gate /* 2*0Sstevel@tonic-gate * CDDL HEADER START 3*0Sstevel@tonic-gate * 4*0Sstevel@tonic-gate * The contents of this file are subject to the terms of the 5*0Sstevel@tonic-gate * Common Development and Distribution License, Version 1.0 only 6*0Sstevel@tonic-gate * (the "License"). You may not use this file except in compliance 7*0Sstevel@tonic-gate * with the License. 8*0Sstevel@tonic-gate * 9*0Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10*0Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing. 11*0Sstevel@tonic-gate * See the License for the specific language governing permissions 12*0Sstevel@tonic-gate * and limitations under the License. 13*0Sstevel@tonic-gate * 14*0Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each 15*0Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16*0Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the 17*0Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying 18*0Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner] 19*0Sstevel@tonic-gate * 20*0Sstevel@tonic-gate * CDDL HEADER END 21*0Sstevel@tonic-gate */ 22*0Sstevel@tonic-gate /* 23*0Sstevel@tonic-gate * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24*0Sstevel@tonic-gate * Use is subject to license terms. 25*0Sstevel@tonic-gate */ 26*0Sstevel@tonic-gate 27*0Sstevel@tonic-gate #pragma ident "%Z%%M% %I% %E% SMI" 28*0Sstevel@tonic-gate 29*0Sstevel@tonic-gate #include "packer.h" 30*0Sstevel@tonic-gate 31*0Sstevel@tonic-gate /* 32*0Sstevel@tonic-gate * This file steers the creation of the Crack Dictionary Database. 33*0Sstevel@tonic-gate * Based on a list of source dictionaries specified by the administrator, 34*0Sstevel@tonic-gate * we create the Database by sorting each dictionary (in memory, one at 35*0Sstevel@tonic-gate * a time), writing the sorted result to a temporary file, and merging 36*0Sstevel@tonic-gate * all the temporary files into the Database. 37*0Sstevel@tonic-gate * 38*0Sstevel@tonic-gate * The current implementation has a number of limitations 39*0Sstevel@tonic-gate * - each single source dictionary has to fit in memory 40*0Sstevel@tonic-gate * - each single source dictionary has to be smaller than 2GByte 41*0Sstevel@tonic-gate * - each single source dictionary can only hold up to 4GB words 42*0Sstevel@tonic-gate * None of these seem real, practical, problems to me. 43*0Sstevel@tonic-gate * 44*0Sstevel@tonic-gate * All of this is meant to be run by one thread per host. The caller is 45*0Sstevel@tonic-gate * responsible for locking things appropriately (as make_dict_database 46*0Sstevel@tonic-gate * in dict.c does). 47*0Sstevel@tonic-gate */ 48*0Sstevel@tonic-gate 49*0Sstevel@tonic-gate #include <stdio.h> 50*0Sstevel@tonic-gate #include <stdlib.h> 51*0Sstevel@tonic-gate #include <unistd.h> 52*0Sstevel@tonic-gate #include <ctype.h> 53*0Sstevel@tonic-gate #include <string.h> 54*0Sstevel@tonic-gate #include <errno.h> 55*0Sstevel@tonic-gate #include <sys/stat.h> 56*0Sstevel@tonic-gate #include <fcntl.h> 57*0Sstevel@tonic-gate 58*0Sstevel@tonic-gate /* Stuff used for sorting the dictionary */ 59*0Sstevel@tonic-gate static char *buf; /* used to hold the source dictionary */ 60*0Sstevel@tonic-gate static uint_t *offsets; /* array of word-offsets into "buf" */ 61*0Sstevel@tonic-gate static uint_t off_idx = 0; /* first free index in offsets array */ 62*0Sstevel@tonic-gate static size_t off_size = 0; /* offsets array size */ 63*0Sstevel@tonic-gate 64*0Sstevel@tonic-gate /* stuff to keep track of the temporary files */ 65*0Sstevel@tonic-gate #define FNAME_TEMPLATE "/var/tmp/authtok_check.XXXXXX" 66*0Sstevel@tonic-gate #define MAXTMP 64 67*0Sstevel@tonic-gate static FILE *tmpfp[MAXTMP]; /* FILE *'s to (unlinked) temporary files */ 68*0Sstevel@tonic-gate static int tmpfp_idx = 0; /* points to first free entry in tmpfp */ 69*0Sstevel@tonic-gate 70*0Sstevel@tonic-gate #define MODNAME "pam_authtok_check::packer" 71*0Sstevel@tonic-gate 72*0Sstevel@tonic-gate /* 73*0Sstevel@tonic-gate * int writeout(void) 74*0Sstevel@tonic-gate * 75*0Sstevel@tonic-gate * Write the sorted wordlist to disk. We create a temporary file 76*0Sstevel@tonic-gate * (in /var/tmp), and immediately unlink() it. We keep an open 77*0Sstevel@tonic-gate * FILE pointer to it in tmpfp[] for later use. 78*0Sstevel@tonic-gate * 79*0Sstevel@tonic-gate * returns 0 on success, -1 on failure (can't create file/output failure). 80*0Sstevel@tonic-gate */ 81*0Sstevel@tonic-gate int 82*0Sstevel@tonic-gate writeout(void) 83*0Sstevel@tonic-gate { 84*0Sstevel@tonic-gate int i = 0; 85*0Sstevel@tonic-gate char tmpname[sizeof (FNAME_TEMPLATE)]; 86*0Sstevel@tonic-gate int fd; 87*0Sstevel@tonic-gate 88*0Sstevel@tonic-gate if (tmpfp_idx == MAXTMP) { 89*0Sstevel@tonic-gate syslog(LOG_ERR, MODNAME ": too many temporary " 90*0Sstevel@tonic-gate "files (maximum %d exceeded)", MAXTMP); 91*0Sstevel@tonic-gate return (-1); 92*0Sstevel@tonic-gate } 93*0Sstevel@tonic-gate 94*0Sstevel@tonic-gate (void) strcpy(tmpname, FNAME_TEMPLATE); 95*0Sstevel@tonic-gate if ((fd = mkstemp(tmpname)) == -1) { 96*0Sstevel@tonic-gate syslog(LOG_ERR, MODNAME ": mkstemp() failed: %s\n", 97*0Sstevel@tonic-gate strerror(errno)); 98*0Sstevel@tonic-gate return (-1); 99*0Sstevel@tonic-gate } 100*0Sstevel@tonic-gate (void) unlink(tmpname); 101*0Sstevel@tonic-gate 102*0Sstevel@tonic-gate if ((tmpfp[tmpfp_idx] = fdopen(fd, "w+")) == NULL) { 103*0Sstevel@tonic-gate syslog(LOG_ERR, MODNAME ": fdopen failed: %s", 104*0Sstevel@tonic-gate strerror(errno)); 105*0Sstevel@tonic-gate (void) close(fd); 106*0Sstevel@tonic-gate return (-1); 107*0Sstevel@tonic-gate } 108*0Sstevel@tonic-gate 109*0Sstevel@tonic-gate /* write words to file */ 110*0Sstevel@tonic-gate while (i < off_idx) { 111*0Sstevel@tonic-gate if (fprintf(tmpfp[tmpfp_idx], "%s\n", &buf[offsets[i++]]) < 0) { 112*0Sstevel@tonic-gate syslog(LOG_ERR, MODNAME ": write to file failed: %s", 113*0Sstevel@tonic-gate strerror(errno)); 114*0Sstevel@tonic-gate (void) close(fd); 115*0Sstevel@tonic-gate return (-1); 116*0Sstevel@tonic-gate } 117*0Sstevel@tonic-gate } 118*0Sstevel@tonic-gate 119*0Sstevel@tonic-gate /* we have one extra tmpfp */ 120*0Sstevel@tonic-gate tmpfp_idx++; 121*0Sstevel@tonic-gate 122*0Sstevel@tonic-gate return (0); 123*0Sstevel@tonic-gate } 124*0Sstevel@tonic-gate 125*0Sstevel@tonic-gate /* 126*0Sstevel@tonic-gate * int insert_word(int off) 127*0Sstevel@tonic-gate * 128*0Sstevel@tonic-gate * insert an offset into the offsets-array. If the offsets-array is out of 129*0Sstevel@tonic-gate * space, we allocate additional space (in CHUNKs) 130*0Sstevel@tonic-gate * 131*0Sstevel@tonic-gate * returns 0 on success, -1 on failure (out of memory) 132*0Sstevel@tonic-gate */ 133*0Sstevel@tonic-gate int 134*0Sstevel@tonic-gate insert_word(int off) 135*0Sstevel@tonic-gate { 136*0Sstevel@tonic-gate #define CHUNK 10000 137*0Sstevel@tonic-gate 138*0Sstevel@tonic-gate if (off_idx == off_size) { 139*0Sstevel@tonic-gate uint_t *tmp; 140*0Sstevel@tonic-gate off_size += CHUNK; 141*0Sstevel@tonic-gate tmp = realloc(offsets, sizeof (uint_t) * off_size); 142*0Sstevel@tonic-gate if (tmp == NULL) { 143*0Sstevel@tonic-gate syslog(LOG_ERR, MODNAME ": out of memory"); 144*0Sstevel@tonic-gate free(offsets); 145*0Sstevel@tonic-gate off_idx = off_size = 0; 146*0Sstevel@tonic-gate offsets = NULL; 147*0Sstevel@tonic-gate return (-1); 148*0Sstevel@tonic-gate } 149*0Sstevel@tonic-gate offsets = tmp; 150*0Sstevel@tonic-gate } 151*0Sstevel@tonic-gate 152*0Sstevel@tonic-gate offsets[off_idx++] = off; 153*0Sstevel@tonic-gate return (0); 154*0Sstevel@tonic-gate } 155*0Sstevel@tonic-gate 156*0Sstevel@tonic-gate /* 157*0Sstevel@tonic-gate * translate(buf, size) 158*0Sstevel@tonic-gate * 159*0Sstevel@tonic-gate * perform "tr '[A-Z]' '[a-z]' | tr -cd '\012[a-z][0-9]'" on the 160*0Sstevel@tonic-gate * words in "buf" and insert each of them into the offsets-array. 161*0Sstevel@tonic-gate * We refrain from using 'isupper' and 'islower' to keep this strictly 162*0Sstevel@tonic-gate * ASCII-only, as is the original Cracklib code. 163*0Sstevel@tonic-gate * 164*0Sstevel@tonic-gate * returns 0 on success, -1 on failure (failure of insert_word) 165*0Sstevel@tonic-gate */ 166*0Sstevel@tonic-gate int 167*0Sstevel@tonic-gate translate(char *buf, size_t size) 168*0Sstevel@tonic-gate { 169*0Sstevel@tonic-gate char *p, *q, *e; 170*0Sstevel@tonic-gate char c; 171*0Sstevel@tonic-gate int wordstart; 172*0Sstevel@tonic-gate 173*0Sstevel@tonic-gate e = &buf[size]; 174*0Sstevel@tonic-gate 175*0Sstevel@tonic-gate wordstart = 0; 176*0Sstevel@tonic-gate for (p = buf, q = buf; q < e; q++) { 177*0Sstevel@tonic-gate c = *q; 178*0Sstevel@tonic-gate if (c >= 'A' && c <= 'Z') { 179*0Sstevel@tonic-gate *(p++) = tolower(c); 180*0Sstevel@tonic-gate } else if (c == '\n') { 181*0Sstevel@tonic-gate *(p++) = '\0'; 182*0Sstevel@tonic-gate /* 183*0Sstevel@tonic-gate * make sure we only insert words consisting of 184*0Sstevel@tonic-gate * MAXWORDLEN-1 bytes or less 185*0Sstevel@tonic-gate */ 186*0Sstevel@tonic-gate if (p-&buf[wordstart] > MAXWORDLEN) 187*0Sstevel@tonic-gate buf[wordstart+MAXWORDLEN-1] = '\0'; 188*0Sstevel@tonic-gate if (insert_word(wordstart) != 0) 189*0Sstevel@tonic-gate return (-1); 190*0Sstevel@tonic-gate wordstart = p-buf; 191*0Sstevel@tonic-gate } else if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { 192*0Sstevel@tonic-gate *(p++) = c; 193*0Sstevel@tonic-gate } 194*0Sstevel@tonic-gate } 195*0Sstevel@tonic-gate return (0); 196*0Sstevel@tonic-gate } 197*0Sstevel@tonic-gate 198*0Sstevel@tonic-gate /* 199*0Sstevel@tonic-gate * int compare(a, b) 200*0Sstevel@tonic-gate * 201*0Sstevel@tonic-gate * helper-routine used for quicksort. we compate two words in the 202*0Sstevel@tonic-gate * buffer, one start starts at index "a", and the other one that starts 203*0Sstevel@tonic-gate * at index "b" 204*0Sstevel@tonic-gate */ 205*0Sstevel@tonic-gate int 206*0Sstevel@tonic-gate compare(const void *a, const void *b) 207*0Sstevel@tonic-gate { 208*0Sstevel@tonic-gate int idx_a = *(uint_t *)a, idx_b = *(uint_t *)b; 209*0Sstevel@tonic-gate 210*0Sstevel@tonic-gate return (strcmp(&buf[idx_a], &buf[idx_b])); 211*0Sstevel@tonic-gate } 212*0Sstevel@tonic-gate 213*0Sstevel@tonic-gate /* 214*0Sstevel@tonic-gate * 215*0Sstevel@tonic-gate * int sort_file(fname) 216*0Sstevel@tonic-gate * 217*0Sstevel@tonic-gate * We sort the file in memory: we read the dictionary file, translate all 218*0Sstevel@tonic-gate * newlines to '\0's, all uppercase ASCII characters to lowercase characters 219*0Sstevel@tonic-gate * and removing all characters but '[a-z][0-9]'. 220*0Sstevel@tonic-gate * We maintain an array of offsets into the buffer where each word starts 221*0Sstevel@tonic-gate * and sort this array using qsort(). 222*0Sstevel@tonic-gate * 223*0Sstevel@tonic-gate * This implements the original cracklib code that did an execl of 224*0Sstevel@tonic-gate * sh -c "/usr/bin/cat <list of files> | 225*0Sstevel@tonic-gate * /usr/bin/tr '[A-Z]' '[a-z]' | /usr/bin/tr -cd '\012[a-z][0-9]' | 226*0Sstevel@tonic-gate * sort -o tmfpfile 227*0Sstevel@tonic-gate * 228*0Sstevel@tonic-gate * returns 0 on success, -1 on failure. 229*0Sstevel@tonic-gate */ 230*0Sstevel@tonic-gate int 231*0Sstevel@tonic-gate sort_file(char *fname) 232*0Sstevel@tonic-gate { 233*0Sstevel@tonic-gate int fd; 234*0Sstevel@tonic-gate struct stat statbuf; 235*0Sstevel@tonic-gate ssize_t n; 236*0Sstevel@tonic-gate int ret = -1; 237*0Sstevel@tonic-gate 238*0Sstevel@tonic-gate if ((fd = open(fname, O_RDONLY)) == -1) { 239*0Sstevel@tonic-gate syslog(LOG_ERR, MODNAME ": failed to open %s: %s", 240*0Sstevel@tonic-gate fname, strerror(errno)); 241*0Sstevel@tonic-gate return (-1); 242*0Sstevel@tonic-gate } 243*0Sstevel@tonic-gate 244*0Sstevel@tonic-gate if (fstat(fd, &statbuf) == -1) { 245*0Sstevel@tonic-gate syslog(LOG_ERR, MODNAME ": fstat() failed (%s)", 246*0Sstevel@tonic-gate strerror(errno)); 247*0Sstevel@tonic-gate (void) close(fd); 248*0Sstevel@tonic-gate return (-1); 249*0Sstevel@tonic-gate } 250*0Sstevel@tonic-gate if ((buf = malloc(statbuf.st_size + 1)) == NULL) { 251*0Sstevel@tonic-gate syslog(LOG_ERR, MODNAME ": out of memory"); 252*0Sstevel@tonic-gate goto error; 253*0Sstevel@tonic-gate } 254*0Sstevel@tonic-gate 255*0Sstevel@tonic-gate n = read(fd, buf, statbuf.st_size); 256*0Sstevel@tonic-gate 257*0Sstevel@tonic-gate if (n == -1) { 258*0Sstevel@tonic-gate if (errno == EINVAL) 259*0Sstevel@tonic-gate syslog(LOG_ERR, MODNAME ": %s is too big. " 260*0Sstevel@tonic-gate "Split the file into smaller files.", fname); 261*0Sstevel@tonic-gate else 262*0Sstevel@tonic-gate syslog(LOG_ERR, MODNAME ": read failed: %s", 263*0Sstevel@tonic-gate strerror(errno)); 264*0Sstevel@tonic-gate goto error; 265*0Sstevel@tonic-gate } 266*0Sstevel@tonic-gate 267*0Sstevel@tonic-gate if (translate(buf, n) == 0) { 268*0Sstevel@tonic-gate qsort((void *)offsets, off_idx, sizeof (int), compare); 269*0Sstevel@tonic-gate 270*0Sstevel@tonic-gate if (writeout() == 0) 271*0Sstevel@tonic-gate ret = 0; 272*0Sstevel@tonic-gate } 273*0Sstevel@tonic-gate 274*0Sstevel@tonic-gate error: 275*0Sstevel@tonic-gate (void) close(fd); 276*0Sstevel@tonic-gate 277*0Sstevel@tonic-gate if (buf != NULL) 278*0Sstevel@tonic-gate free(buf); 279*0Sstevel@tonic-gate if (offsets != NULL) 280*0Sstevel@tonic-gate free(offsets); 281*0Sstevel@tonic-gate offsets = NULL; 282*0Sstevel@tonic-gate off_size = 0; 283*0Sstevel@tonic-gate off_idx = 0; 284*0Sstevel@tonic-gate return (ret); 285*0Sstevel@tonic-gate } 286*0Sstevel@tonic-gate 287*0Sstevel@tonic-gate /* 288*0Sstevel@tonic-gate * We merge the temporary files created by previous calls to sort_file() 289*0Sstevel@tonic-gate * and insert the thus sorted words into the cracklib database 290*0Sstevel@tonic-gate */ 291*0Sstevel@tonic-gate void 292*0Sstevel@tonic-gate merge_files(PWDICT *pwp) 293*0Sstevel@tonic-gate { 294*0Sstevel@tonic-gate int ti; 295*0Sstevel@tonic-gate char *words[MAXTMP]; 296*0Sstevel@tonic-gate char lastword[MAXWORDLEN]; 297*0Sstevel@tonic-gate int choice; 298*0Sstevel@tonic-gate 299*0Sstevel@tonic-gate lastword[0] = '\0'; 300*0Sstevel@tonic-gate 301*0Sstevel@tonic-gate for (ti = 0; ti < tmpfp_idx; ti++) 302*0Sstevel@tonic-gate words[ti] = malloc(MAXWORDLEN); 303*0Sstevel@tonic-gate /* 304*0Sstevel@tonic-gate * we read the first word of each of the temp-files into words[]. 305*0Sstevel@tonic-gate */ 306*0Sstevel@tonic-gate for (ti = 0; ti < tmpfp_idx; ti++) { 307*0Sstevel@tonic-gate (void) fseek(tmpfp[ti], 0, SEEK_SET); 308*0Sstevel@tonic-gate (void) fgets(words[ti], MAXWORDLEN, tmpfp[ti]); 309*0Sstevel@tonic-gate words[ti][MAXWORDLEN-1] = '\0'; 310*0Sstevel@tonic-gate } 311*0Sstevel@tonic-gate 312*0Sstevel@tonic-gate /* 313*0Sstevel@tonic-gate * next, we emit the word that comes first (lexicographically), 314*0Sstevel@tonic-gate * and replace that word with a new word from the file it 315*0Sstevel@tonic-gate * came from. If the file is exhausted, we close the fp and 316*0Sstevel@tonic-gate * swap the fp with the last fp in tmpfp[]. 317*0Sstevel@tonic-gate * we then decrease tmpfp_idx and continue with what's left until 318*0Sstevel@tonic-gate * we run out of open FILE pointers. 319*0Sstevel@tonic-gate */ 320*0Sstevel@tonic-gate while (tmpfp_idx != 0) { 321*0Sstevel@tonic-gate choice = 0; 322*0Sstevel@tonic-gate 323*0Sstevel@tonic-gate for (ti = 1; ti < tmpfp_idx; ti++) 324*0Sstevel@tonic-gate if (strcmp(words[choice], words[ti]) > 0) 325*0Sstevel@tonic-gate choice = ti; 326*0Sstevel@tonic-gate /* Insert word in Cracklib database */ 327*0Sstevel@tonic-gate (void) Chomp(words[choice]); 328*0Sstevel@tonic-gate if (words[choice][0] != '\0' && 329*0Sstevel@tonic-gate strcmp(lastword, words[choice]) != 0) { 330*0Sstevel@tonic-gate (void) PutPW(pwp, words[choice]); 331*0Sstevel@tonic-gate (void) strncpy(lastword, words[choice], MAXWORDLEN); 332*0Sstevel@tonic-gate } 333*0Sstevel@tonic-gate 334*0Sstevel@tonic-gate if (fgets(words[choice], MAXWORDLEN, tmpfp[choice]) == NULL) { 335*0Sstevel@tonic-gate (void) fclose(tmpfp[choice]); 336*0Sstevel@tonic-gate tmpfp[choice] = tmpfp[tmpfp_idx - 1]; 337*0Sstevel@tonic-gate tmpfp_idx--; 338*0Sstevel@tonic-gate } else 339*0Sstevel@tonic-gate words[choice][MAXWORDLEN-1] = '\0'; 340*0Sstevel@tonic-gate } 341*0Sstevel@tonic-gate } 342*0Sstevel@tonic-gate 343*0Sstevel@tonic-gate /* 344*0Sstevel@tonic-gate * int packer(list) 345*0Sstevel@tonic-gate * 346*0Sstevel@tonic-gate * sort all dictionaries in "list", and feed the words into the Crack 347*0Sstevel@tonic-gate * Password Database. 348*0Sstevel@tonic-gate * 349*0Sstevel@tonic-gate * returns 0 on sucess, -1 on failure. 350*0Sstevel@tonic-gate */ 351*0Sstevel@tonic-gate int 352*0Sstevel@tonic-gate packer(char *list, char *path) 353*0Sstevel@tonic-gate { 354*0Sstevel@tonic-gate PWDICT *pwp; 355*0Sstevel@tonic-gate char *listcopy, *fname; 356*0Sstevel@tonic-gate int ret = 0; 357*0Sstevel@tonic-gate 358*0Sstevel@tonic-gate if ((listcopy = strdup(list)) == NULL) { 359*0Sstevel@tonic-gate syslog(LOG_ERR, MODNAME ": out of memory"); 360*0Sstevel@tonic-gate return (-1); 361*0Sstevel@tonic-gate } 362*0Sstevel@tonic-gate 363*0Sstevel@tonic-gate if (!(pwp = PWOpen(path, "w"))) 364*0Sstevel@tonic-gate return (-1); 365*0Sstevel@tonic-gate 366*0Sstevel@tonic-gate fname = strtok(listcopy, " \t,"); 367*0Sstevel@tonic-gate while (ret == 0 && fname != NULL) { 368*0Sstevel@tonic-gate if ((ret = sort_file(fname)) == 0) 369*0Sstevel@tonic-gate fname = strtok(NULL, " \t,"); 370*0Sstevel@tonic-gate } 371*0Sstevel@tonic-gate free(listcopy); 372*0Sstevel@tonic-gate 373*0Sstevel@tonic-gate if (ret == 0) 374*0Sstevel@tonic-gate merge_files(pwp); 375*0Sstevel@tonic-gate 376*0Sstevel@tonic-gate (void) PWClose(pwp); 377*0Sstevel@tonic-gate 378*0Sstevel@tonic-gate return (ret); 379*0Sstevel@tonic-gate } 380