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 }