4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
29 * This file steers the creation of the Crack Dictionary Database.
30 * Based on a list of source dictionaries specified by the administrator,
31 * we create the Database by sorting each dictionary (in memory, one at
32 * a time), writing the sorted result to a temporary file, and merging
33 * all the temporary files into the Database.
35 * The current implementation has a number of limitations
36 * - each single source dictionary has to fit in memory
37 * - each single source dictionary has to be smaller than 2GByte
38 * - each single source dictionary can only hold up to 4GB words
39 * None of these seem real, practical, problems to me.
41 * All of this is meant to be run by one thread per host. The caller is
42 * responsible for locking things appropriately (as make_dict_database
55 /* Stuff used for sorting the dictionary */
56 static char *buf
; /* used to hold the source dictionary */
57 static uint_t
*offsets
; /* array of word-offsets into "buf" */
58 static uint_t off_idx
= 0; /* first free index in offsets array */
59 static size_t off_size
= 0; /* offsets array size */
61 /* stuff to keep track of the temporary files */
62 #define FNAME_TEMPLATE "/var/tmp/authtok_check.XXXXXX"
64 static FILE *tmpfp
[MAXTMP
]; /* FILE *'s to (unlinked) temporary files */
65 static int tmpfp_idx
= 0; /* points to first free entry in tmpfp */
67 #define MODNAME "pam_authtok_check::packer"
72 * Write the sorted wordlist to disk. We create a temporary file
73 * (in /var/tmp), and immediately unlink() it. We keep an open
74 * FILE pointer to it in tmpfp[] for later use.
76 * returns 0 on success, -1 on failure (can't create file/output failure).
82 char tmpname
[sizeof (FNAME_TEMPLATE
)];
85 if (tmpfp_idx
== MAXTMP
) {
86 syslog(LOG_ERR
, MODNAME
": too many temporary "
87 "files (maximum %d exceeded)", MAXTMP
);
91 (void) strcpy(tmpname
, FNAME_TEMPLATE
);
92 if ((fd
= mkstemp(tmpname
)) == -1) {
93 syslog(LOG_ERR
, MODNAME
": mkstemp() failed: %s\n",
97 (void) unlink(tmpname
);
99 if ((tmpfp
[tmpfp_idx
] = fdopen(fd
, "w+F")) == NULL
) {
100 syslog(LOG_ERR
, MODNAME
": fdopen failed: %s",
106 /* write words to file */
107 while (i
< off_idx
) {
108 if (fprintf(tmpfp
[tmpfp_idx
], "%s\n", &buf
[offsets
[i
++]]) < 0) {
109 syslog(LOG_ERR
, MODNAME
": write to file failed: %s",
116 /* we have one extra tmpfp */
123 * int insert_word(int off)
125 * insert an offset into the offsets-array. If the offsets-array is out of
126 * space, we allocate additional space (in CHUNKs)
128 * returns 0 on success, -1 on failure (out of memory)
135 if (off_idx
== off_size
) {
138 tmp
= reallocarray(offsets
, off_size
, sizeof (uint_t
));
140 syslog(LOG_ERR
, MODNAME
": out of memory");
142 off_idx
= off_size
= 0;
149 offsets
[off_idx
++] = off
;
154 * translate(buf, size)
156 * perform "tr '[A-Z]' '[a-z]' | tr -cd '\012[a-z][0-9]'" on the
157 * words in "buf" and insert each of them into the offsets-array.
158 * We refrain from using 'isupper' and 'islower' to keep this strictly
159 * ASCII-only, as is the original Cracklib code.
161 * returns 0 on success, -1 on failure (failure of insert_word)
164 translate(char *buf
, size_t size
)
173 for (p
= buf
, q
= buf
; q
< e
; q
++) {
175 if (c
>= 'A' && c
<= 'Z') {
177 } else if (c
== '\n') {
180 * make sure we only insert words consisting of
181 * MAXWORDLEN-1 bytes or less
183 if (p
-&buf
[wordstart
] > MAXWORDLEN
)
184 buf
[wordstart
+MAXWORDLEN
-1] = '\0';
185 if (insert_word(wordstart
) != 0)
188 } else if ((c
>= 'a' && c
<= 'z') || (c
>= '0' && c
<= '9')) {
198 * helper-routine used for quicksort. we compate two words in the
199 * buffer, one start starts at index "a", and the other one that starts
203 compare(const void *a
, const void *b
)
205 int idx_a
= *(uint_t
*)a
, idx_b
= *(uint_t
*)b
;
207 return (strcmp(&buf
[idx_a
], &buf
[idx_b
]));
212 * int sort_file(fname)
214 * We sort the file in memory: we read the dictionary file, translate all
215 * newlines to '\0's, all uppercase ASCII characters to lowercase characters
216 * and removing all characters but '[a-z][0-9]'.
217 * We maintain an array of offsets into the buffer where each word starts
218 * and sort this array using qsort().
220 * This implements the original cracklib code that did an execl of
221 * sh -c "/usr/bin/cat <list of files> |
222 * /usr/bin/tr '[A-Z]' '[a-z]' | /usr/bin/tr -cd '\012[a-z][0-9]' |
225 * returns 0 on success, -1 on failure.
228 sort_file(char *fname
)
235 if ((fd
= open(fname
, O_RDONLY
)) == -1) {
236 syslog(LOG_ERR
, MODNAME
": failed to open %s: %s",
237 fname
, strerror(errno
));
241 if (fstat(fd
, &statbuf
) == -1) {
242 syslog(LOG_ERR
, MODNAME
": fstat() failed (%s)",
247 if ((buf
= malloc(statbuf
.st_size
+ 1)) == NULL
) {
248 syslog(LOG_ERR
, MODNAME
": out of memory");
252 n
= read(fd
, buf
, statbuf
.st_size
);
256 syslog(LOG_ERR
, MODNAME
": %s is too big. "
257 "Split the file into smaller files.", fname
);
259 syslog(LOG_ERR
, MODNAME
": read failed: %s",
264 if (translate(buf
, n
) == 0) {
265 qsort((void *)offsets
, off_idx
, sizeof (int), compare
);
283 * We merge the temporary files created by previous calls to sort_file()
284 * and insert the thus sorted words into the cracklib database
286 * returns 0 on success, -1 on failure.
289 merge_files(PWDICT
*pwp
)
293 char lastword
[MAXWORDLEN
];
298 for (ti
= 0; ti
< tmpfp_idx
; ti
++)
299 if ((words
[ti
] = malloc(MAXWORDLEN
)) == NULL
) {
306 * we read the first word of each of the temp-files into words[].
308 for (ti
= 0; ti
< tmpfp_idx
; ti
++) {
309 (void) fseek(tmpfp
[ti
], 0, SEEK_SET
);
310 (void) fgets(words
[ti
], MAXWORDLEN
, tmpfp
[ti
]);
311 words
[ti
][MAXWORDLEN
-1] = '\0';
315 * next, we emit the word that comes first (lexicographically),
316 * and replace that word with a new word from the file it
317 * came from. If the file is exhausted, we close the fp and
318 * swap the fp with the last fp in tmpfp[].
319 * we then decrease tmpfp_idx and continue with what's left until
320 * we run out of open FILE pointers.
322 while (tmpfp_idx
!= 0) {
325 for (ti
= 1; ti
< tmpfp_idx
; ti
++)
326 if (strcmp(words
[choice
], words
[ti
]) > 0)
328 /* Insert word in Cracklib database */
329 (void) Chomp(words
[choice
]);
330 if (words
[choice
][0] != '\0' &&
331 strcmp(lastword
, words
[choice
]) != 0) {
332 (void) PutPW(pwp
, words
[choice
]);
333 (void) strncpy(lastword
, words
[choice
], MAXWORDLEN
);
336 if (fgets(words
[choice
], MAXWORDLEN
, tmpfp
[choice
]) == NULL
) {
337 (void) fclose(tmpfp
[choice
]);
338 tmpfp
[choice
] = tmpfp
[tmpfp_idx
- 1];
341 words
[choice
][MAXWORDLEN
-1] = '\0';
349 * sort all dictionaries in "list", and feed the words into the Crack
352 * returns 0 on sucess, -1 on failure.
355 packer(char *list
, char *path
)
358 char *listcopy
, *fname
;
361 if ((listcopy
= strdup(list
)) == NULL
) {
362 syslog(LOG_ERR
, MODNAME
": out of memory");
366 if (!(pwp
= PWOpen(path
, "wF")))
369 fname
= strtok(listcopy
, " \t,");
370 while (ret
== 0 && fname
!= NULL
) {
371 if ((ret
= sort_file(fname
)) == 0)
372 fname
= strtok(NULL
, " \t,");
377 ret
= merge_files(pwp
);