xref: /illumos-gate/usr/src/lib/pam_modules/authtok_check/dict.c (revision ed5289f91b9bf164dccd6c75398362be77a4478d)
1 /*
2  * CDDL HEADER START
3  *
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.
7  *
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.
12  *
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]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <sys/stat.h>
29 #include <stdio.h>
30 #include <syslog.h>
31 #include <fcntl.h>
32 #include <time.h>
33 #include <errno.h>
34 #include <signal.h>
35 #include "packer.h"
36 
37 static int lockfd = -1;
38 static struct flock flock = { 0, 0, 0, 0, 0, 0 };
39 
40 char dblock[PATH_MAX];
41 
42 #define	LOCK_WAIT	1000000
43 #define	LOCK_RETRIES	60
44 
45 /*
46  * lock_db()
47  *
48  * Create a lockfile to prevent simultaneous access to the database
49  * creation routines. We set a timeout to LOCK_WAIT seconds. If we
50  * haven't obtained a lock after LOCK_RETIRES attempts, we bail out.
51  *
52  * returns 0 on succes, -1 on (lock) failure.
53  * side effect: the directory "path" will be created if it didn't exist.
54  */
55 int
56 lock_db(char *path)
57 {
58 	int retval;
59 	struct stat st;
60 	int retries = 0;
61 
62 	/* create directory "path" if it doesn't exist */
63 	if (stat(path, &st) == -1) {
64 		if (errno != ENOENT ||
65 		    (mkdir(path, 0755) == -1 || chmod(path, 0755) == -1))
66 			return (-1);
67 	}
68 
69 	(void) snprintf(dblock, sizeof (dblock), "%s/authtok_check.lock", path);
70 
71 	if ((lockfd = open(dblock, O_WRONLY|O_CREAT|O_EXCL, 0400)) == -1) {
72 		if (errno == EEXIST)
73 			lockfd = open(dblock, O_WRONLY);
74 		if (lockfd == -1) {
75 			int olderrno = errno;
76 			syslog(LOG_ERR, "pam_authtok_check::pam_sm_chauthtok: "
77 			    "can't open lockfile: %s", strerror(errno));
78 			errno = olderrno;
79 			return (-1);
80 		}
81 	}
82 
83 	do {
84 		flock.l_type = F_WRLCK;
85 		retval = fcntl(lockfd, F_SETLK, &flock);
86 		if (retval == -1)
87 			(void) usleep(LOCK_WAIT);
88 	} while (retval == -1 && ++retries < LOCK_RETRIES);
89 
90 	if (retval == -1) {
91 		int errno_saved = errno;
92 		syslog(LOG_ERR, "pam_authtok_check::pam_sm_chauthtok: timeout "
93 		    "waiting for dictionary lock.");
94 		errno = errno_saved;
95 	}
96 
97 	return (retval);
98 }
99 
100 /*
101  * unlock_db()
102  *
103  * Release the database lock
104  */
105 void
106 unlock_db(void)
107 {
108 	if (lockfd != -1) {
109 		flock.l_type = F_UNLCK;
110 		(void) fcntl(lockfd, F_SETLK, &flock);
111 		(void) close(lockfd);
112 		lockfd = -1;
113 	}
114 }
115 
116 /*
117  * database_present()
118  *
119  * returns 0 if the database files are found, and the database size is
120  * greater than 0
121  */
122 int
123 database_present(char *path)
124 {
125 	struct stat st;
126 	char dict_hwm[PATH_MAX];
127 	char dict_pwd[PATH_MAX];
128 	char dict_pwi[PATH_MAX];
129 
130 	(void) snprintf(dict_hwm, sizeof (dict_hwm), "%s/%s", path,
131 	    DICT_DATABASE_HWM);
132 	(void) snprintf(dict_pwd, sizeof (dict_pwd), "%s/%s", path,
133 	    DICT_DATABASE_PWD);
134 	(void) snprintf(dict_pwi, sizeof (dict_pwi), "%s/%s", path,
135 	    DICT_DATABASE_PWI);
136 
137 	if (stat(dict_hwm, &st) == -1 ||
138 	    (stat(dict_pwd, &st) == -1 || st.st_size == 0) ||
139 	    stat(dict_pwi, &st) == -1)
140 		return (NO_DICTDATABASE);
141 
142 	return (0);
143 }
144 
145 /*
146  * build_dict_database(list, char *path)
147  *
148  * Create the Crack Dictionary Database based on the list of sources
149  * dictionaries specified in "list". Store the database in "path".
150  */
151 int
152 build_dict_database(char *list, char *path)
153 {
154 	return (packer(list, path) == -1 ? DICTDATABASE_BUILD_ERR : 0);
155 }
156 
157 /*
158  * Rebuild the database in "path" if the database is older than one of the
159  * files listed in "list", or older than the config-file PWADMIN.
160  */
161 int
162 update_dict_database(char *list, char *path)
163 {
164 	struct stat st_db;
165 	struct stat st_file;
166 	char *buf;
167 	char *listcopy;
168 	boolean_t update_needed = B_FALSE;
169 	char dbase_pwd[PATH_MAX];
170 
171 	(void) snprintf(dbase_pwd, sizeof (dbase_pwd), "%s/%s", path,
172 	    DICT_DATABASE_PWD);
173 
174 	if (stat(dbase_pwd, &st_db) == -1)
175 		return (DICTFILE_ERR);
176 
177 	if ((listcopy = strdup(list)) == NULL)
178 		return (DICTFILE_ERR);
179 
180 	buf = strtok(listcopy,  "\t ,");
181 
182 	/* Compare mtime of each listed dictionary against DB mtime */
183 	while (update_needed == B_FALSE && buf != NULL) {
184 		if (stat(buf, &st_file) == -1) {
185 			if (errno == ENOENT) {
186 				syslog(LOG_ERR,
187 				    "pam_authtok_check:update_dict_database: "
188 				    "dictionary \"%s\" not present.", buf);
189 			} else {
190 				syslog(LOG_ERR,
191 				    "pam_authtok_check:update_dict_database: "
192 				    "stat(%s) failed: %s", buf,
193 				    strerror(errno));
194 			}
195 			free(listcopy);
196 			return (DICTFILE_ERR);
197 		}
198 		if (st_db.st_mtime < st_file.st_mtime)
199 			update_needed = B_TRUE;	/* database out of date */
200 		buf = strtok(NULL, "\t ,");
201 	}
202 
203 	free(listcopy);
204 
205 	/*
206 	 * If /etc/default/passwd is updated, generate the database.
207 	 * Because this is the only way we can check if files have been
208 	 * added/deleted from DICTIONLIST.
209 	 * Drawback: the database will also be generated when other
210 	 * tunables are changed.
211 	 */
212 	if (stat(PWADMIN, &st_file) != -1 && st_db.st_mtime < st_file.st_mtime)
213 		update_needed = B_TRUE;
214 
215 	if (update_needed) {
216 		/*
217 		 * Since we actually rebuild the database, we need to remove
218 		 * the old database first.
219 		 */
220 		PWRemove(path);
221 		return (build_dict_database(list, path));
222 	}
223 
224 	return (0);
225 }
226 
227 /*
228  * Build or update database, while holding the global lock.
229  */
230 int
231 make_dict_database(char *list, char *path)
232 {
233 	int r = -1;
234 
235 	if (lock_db(path) == 0) {
236 		if (database_present(path) == NO_DICTDATABASE)
237 			r = build_dict_database(list, path);
238 		else
239 			r = update_dict_database(list, path);
240 		unlock_db();
241 	}
242 	return (r);
243 }
244