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 /*
23 * Copyright 2012 Joshua M. Clulow <josh@sysmgr.org>
24 */
25
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <errno.h>
31 #include <err.h>
32 #include <string.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <sys/mman.h>
36
37 #define PAM_CONFIG "/etc/pam.conf"
38 #define PAM_CONFIG_DIR "/etc/pam.d"
39
40 #define SENTINEL0 " CDDL HEADER END"
41 #define SENTINEL1 " present in this file in previous releases are " \
42 "still acceptable."
43 #define SENTINEL2 " Authentication management"
44
45 #define LEGACY_CONTENTS "#\n" \
46 "# Legacy PAM Configuration\n" \
47 "#\n" \
48 "# The shipped PAM configuration has moved from " \
49 "the legacy " PAM_CONFIG "\n" \
50 "# to the new " PAM_CONFIG_DIR " model. See " \
51 "pam.conf(4) for more information.\n"
52
53
54 typedef struct pamline {
55 char *service;
56 char *copyfrom;
57 char *commentfrom;
58 struct pamline *next;
59 } pamline_t;
60
61 typedef struct servicelist {
62 char *service;
63 struct servicelist *next;
64 } servicelist_t;
65
66
67 static int fd;
68 static char *f;
69 static size_t flen;
70
71 static pamline_t *all = NULL;
72 static servicelist_t *services = NULL;
73
74 static char *pam_config = PAM_CONFIG;
75 static char *pam_config_dir = PAM_CONFIG_DIR;
76
77 static boolean_t preview = B_FALSE;
78 static boolean_t verbose = B_FALSE;
79 static boolean_t replace_original = B_FALSE;
80
81 static boolean_t onlycomments = B_TRUE;
82
83
84 static void
85 store_service(char *service)
86 {
87 servicelist_t *sl = calloc(1, sizeof (servicelist_t));
88 sl->service = service;
89 if (services == NULL) {
90 services = sl;
91 } else {
92 servicelist_t *t = services;
93 while (t != NULL) {
94 if (strcmp(t->service, sl->service) == 0)
95 return;
96 if (t->next == NULL) {
97 t->next = sl;
98 break;
99 }
100 t = t->next;
101 }
102 }
103 }
104
105 static void
106 print_service(FILE *file, char *service)
107 {
108 pamline_t *t = all;
109 while (t != NULL) {
110 if (t->service && strcmp(t->service, service) == 0) {
111 if (t->copyfrom && fprintf(file, "%s",
112 t->copyfrom) < 0)
113 err(2, "could not write file");
114 if (t->commentfrom && fprintf(file, "#%s",
115 t->commentfrom) < 0)
116 err(2, "could not write file");
117 if (fprintf(file, "\n") < 0)
118 err(2, "could not write file");
119 }
120 t = t->next;
121 }
122 }
123
124 static void
125 write_service_file(char *service)
126 {
127 FILE *out;
128 char *path;
129
130 if (asprintf(&path, "%s/%s", pam_config_dir, service) < 0)
131 err(2, "could not asprintf");
132 if (verbose)
133 (void) fprintf(stderr, "service '%s' -> %s\n", service, path);
134 out = fopen(path, "w+");
135 if (out == NULL)
136 err(2, "could not open %s for write", path);
137 free(path);
138
139 print_service(out, service);
140
141 (void) fclose(out);
142 }
143
144 static void
145 write_legacy_file(void)
146 {
147 FILE *out;
148
149 if (verbose)
150 (void) fprintf(stderr, "replacing %s with placeholder file",
151 pam_config);
152
153 out = fopen(pam_config, "w+");
154 if (out == NULL)
155 err(2, "could not open %s for write", pam_config);
156
157 fprintf(out, LEGACY_CONTENTS);
158
159 (void) fclose(out);
160 }
161
162 static void
163 store_pamline(pamline_t *add)
164 {
165 if ((add->service && strlen(add->service) > 0) ||
166 (add->copyfrom && strlen(add->copyfrom) > 0))
167 onlycomments = B_FALSE;
168
169 /*
170 * If we find the end of various known header block strings,
171 * and we've thus far only seen a block comment, then turf
172 * all existing lines.
173 */
174 if (all && onlycomments && add->commentfrom &&
175 (strcmp(SENTINEL0, add->commentfrom) == 0 ||
176 strcmp(SENTINEL1, add->commentfrom) == 0 ||
177 strcmp(SENTINEL2, add->commentfrom) == 0)) {
178 pamline_t *pres, *t = all;
179 while (t != NULL) {
180 pres = t->next;
181 free(t);
182 t = pres;
183 }
184 all = NULL;
185 free(add);
186 return;
187 }
188
189 if (add->service != NULL)
190 store_service(add->service);
191
192 if (all == NULL) {
193 all = add;
194 } else {
195 pamline_t *t = all;
196 while (t != NULL) {
197 if (add->service != NULL && t->service == NULL) {
198 /* apply this service to all unclaimed lines */
199 t->service = add->service;
200 }
201 if (t->next == NULL) {
202 t->next = add;
203 break;
204 }
205 t = t->next;
206 }
207 }
208 }
209
210 static void
211 open_pam_conf(char *filename)
212 {
213 struct stat st;
214
215 fd = open(filename, O_RDONLY);
216 if (fd == -1) {
217 /* If there is no /etc/pam.conf, then silently exit. */
218 if (errno == ENOENT) {
219 if (verbose)
220 (void) fprintf(stderr, "no %s found;"
221 " not running.\n", pam_config);
222 exit(0);
223 } else {
224 err(2, "could not open %s", filename);
225 }
226 }
227
228 if (fstat(fd, &st) == -1)
229 err(2, "could not stat %s", filename);
230 flen = st.st_size;
231
232 f = mmap(NULL, flen, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
233 if (f == MAP_FAILED)
234 err(2, "could not mmap %s", filename);
235 }
236
237 static void
238 close_pam_conf(void)
239 {
240 (void) munmap(f, flen);
241 (void) close(fd);
242 }
243
244 static pamline_t *
245 new_pamline(void)
246 {
247 pamline_t *pl = calloc(1, sizeof (pamline_t));
248 if (pl == NULL)
249 abort();
250 return (pl);
251 }
252
253 static void
254 find_lines(void)
255 {
256 char *pos;
257 int state = 0;
258 pamline_t *t = new_pamline();
259 boolean_t prelimwhitespace = B_FALSE;
260
261 t->copyfrom = f;
262 for (pos = f; pos < f + flen; pos++) {
263 switch (state) {
264 case 0:
265 switch (*pos) {
266 case '#':
267 *pos = '\0';
268
269 t->commentfrom = pos + 1;
270
271 prelimwhitespace = B_FALSE;
272 state = 1;
273 break;
274 case ' ':
275 case '\t':
276 *pos = '\0';
277
278 t->service = t->copyfrom;
279 t->copyfrom = pos + 1;
280
281 prelimwhitespace = B_TRUE;
282 state = 1;
283 break;
284 case '\n':
285 *pos = '\0';
286
287 store_pamline(t);
288
289 t = new_pamline();
290 t->copyfrom = pos + 1;
291 break;
292 }
293 break;
294 case 1:
295 switch (*pos) {
296 case ' ':
297 case '\t':
298 if (prelimwhitespace)
299 t->copyfrom++;
300 break;
301 case '\n':
302 *pos = '\0';
303
304 store_pamline(t);
305
306 t = new_pamline();
307 t->copyfrom = pos + 1;
308
309 state = 0;
310 break;
311 default:
312 prelimwhitespace = B_FALSE;
313 }
314 }
315 }
316 }
317
318 static void
319 output_services(void)
320 {
321 servicelist_t *s = services;
322
323 while (s != NULL) {
324 if (preview) {
325 (void) fprintf(stdout, "------------ %s/%s :\n",
326 pam_config_dir, s->service);
327 print_service(stdout, s->service);
328 (void) fprintf(stdout, "\n");
329 } else {
330 write_service_file(s->service);
331 }
332 s = s->next;
333 }
334 }
335
336 int
337 main(int argc, char **argv)
338 {
339 int c;
340
341 while ((c = getopt(argc, argv, ":nvri:o:")) != -1) {
342 switch (c) {
343 case 'v':
344 verbose = B_TRUE;
345 break;
346 case 'n':
347 preview = B_TRUE;
348 break;
349 case 'r':
350 replace_original = B_TRUE;
351 break;
352 case 'i':
353 pam_config = optarg;
354 break;
355 case 'o':
356 pam_config_dir = optarg;
357 break;
358 case ':':
359 errx(1, "Option -%c requires an operand", optopt);
360 break;
361 case '?':
362 errx(1, "Unrecognised option: -%c", optopt);
363 break;
364 }
365 }
366
367 if (verbose) {
368 (void) fprintf(stderr, "input file: %s\n", pam_config);
369 (void) fprintf(stderr, "output dir: %s\n", pam_config_dir);
370 }
371
372 open_pam_conf(pam_config);
373 find_lines();
374
375 /*
376 * If we haven't found any non-trivial lines, then exit now.
377 */
378 if (onlycomments) {
379 if (verbose)
380 (void) fprintf(stderr, "trivial %s detected; not "
381 "running.\n", pam_config);
382 goto done;
383 }
384
385 output_services();
386
387 if (!preview && replace_original)
388 write_legacy_file();
389
390 done:
391 close_pam_conf();
392 return (0);
393 }