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 }