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 <string.h>
  28 #include <strings.h>
  29 #include <errno.h>
  30 #include <unistd.h>
  31 #include <limits.h>
  32 #include <fcntl.h>
  33 #include <err.h>
  34 #include <locale.h>
  35 #include <libintl.h>
  36 #include <sys/types.h>
  37 #include <sys/stat.h>
  38 #include <sys/wait.h>
  39 #include <dirent.h>
  40 #include <stddef.h>
  41 #include <libscf.h>
  42 
  43 #include "utils.h"
  44 #include "scf.h"
  45 
  46 #ifndef TEXT_DOMAIN
  47 #define TEXT_DOMAIN     "SYS_TEST"
  48 #endif
  49 
  50 #define _(x)    gettext(x)
  51 
  52 #define CERTS_FILE_DIR  "/etc/certs/CA"
  53 #define CERTS_LINK_DIR  "/etc/openssl/certs"
  54 
  55 #define OPENSSL "/usr/bin/openssl"
  56 
  57 #define FMT_VERSION     "OpenSSL Version: %s (%s)\n"
  58 #define MSG_OLD_HASH    "supports old hash only"
  59 #define MSG_NEW_HASH    "supports new hashes"
  60 
  61 #define FMT_WRONG       "WRONG: [%s] -> %s\n  should be: %s\n"
  62 #define FMT_MISSING     "MISSING: [%s] -> %s\n"
  63 #define FMT_DANGLING    "DANGLING: [%s] -> %s\n"
  64 #define FMT_UNKNOWN     "UNKNOWN TARGET: [%s] -> %s\n"
  65 
  66 #define MAX_HASHES 2
  67 
  68 typedef struct cert_file {
  69         char *path;
  70         char *hash[MAX_HASHES];
  71         uint8_t hashcnt;
  72 
  73         struct cert_file *next;
  74 } cert_file_t;
  75 
  76 typedef struct cert_link {
  77         char *path;
  78         char *hash;
  79         uint8_t targetfound;
  80 
  81         struct cert_link *next;
  82 } cert_link_t;
  83 
  84 /* configuration from smf(5) */
  85 static char *certs_file_dir = CERTS_FILE_DIR;
  86 static char *certs_link_dir = CERTS_LINK_DIR;
  87 static char *openssl = OPENSSL;
  88 static int verbose = B_FALSE;
  89 static boolean_t remove_dangling = B_TRUE;
  90 static boolean_t remove_unknown = B_FALSE;
  91 static boolean_t create_missing = B_TRUE;
  92 static boolean_t correct_wrong = B_TRUE;
  93 
  94 /* list heads */
  95 static cert_file_t *file0 = NULL;
  96 static cert_link_t *link0 = NULL;
  97 
  98 #define OP_HASH         0
  99 #define OP_VERSION      1
 100 #define OP_HASH_OLD     2
 101 #define NUM_OPS         3
 102 char *
 103 run_openssl(unsigned int op, char *arg)
 104 {
 105         pid_t pid;
 106         int des[2];
 107         int i;
 108         ssize_t num;
 109         char buf[500];
 110         int status;
 111 
 112         if (op >= NUM_OPS)
 113                 abort();
 114 
 115         (void) pipe(des);
 116         switch (pid = fork()) {
 117         case -1: /* error */
 118                 err(SMF_EXIT_ERR_FATAL, _("forking child for openssl"));
 119                 break;
 120         case 0: /* child */
 121                 (void) close(des[0]);
 122                 if (dup2(des[1], STDOUT_FILENO) == -1 || dup2(des[1],
 123                     STDERR_FILENO) == -1)
 124                         err(100, NULL);
 125                 (void) close(STDIN_FILENO);
 126                 switch (op) {
 127                 case OP_HASH:
 128                         execlp(openssl, openssl, "x509", "-hash",
 129                             "-noout", "-in", arg, (char *)0);
 130                         break;
 131                 case OP_VERSION:
 132                         execlp(openssl, openssl, "version", (char *)0);
 133                         break;
 134                 case OP_HASH_OLD:
 135                         execlp(openssl, openssl, "x509",
 136                             "-subject_hash_old", "-noout", "-in",
 137                             arg, (char *)0);
 138                         break;
 139                 }
 140                 printf(_("exec openssl failed: %s\n"), strerror(errno));
 141                 exit(SMF_EXIT_ERR_FATAL);
 142                 break;
 143         default: /* parent */
 144                 (void) close(des[1]);
 145                 num = read(des[0], &buf, sizeof (buf));
 146                 (void) close(des[0]);
 147 
 148                 if (num < 0)
 149                         err(101, _("reading from openssl child"));
 150 
 151                 buf[num] = 0;
 152                 for (i = 0; i < num; i++) {
 153                         if (buf[i] == '\n' || buf[i] == '\r') {
 154                                 buf[i] = 0;
 155                                 break;
 156                         }
 157                 }
 158 
 159                 if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status))
 160                         err(SMF_EXIT_ERR_FATAL,
 161                             _("waiting for openssl child"));
 162                 if (WEXITSTATUS(status) != 0)
 163                         errx(SMF_EXIT_ERR_FATAL,
 164                             _("error from openssl child (rc: %d): %s"),
 165                             WEXITSTATUS(status), buf);
 166 
 167                 return (xstrdup(buf));
 168         }
 169 }
 170 
 171 int newossl = 0;
 172 
 173 
 174 void
 175 detect_openssl(void)
 176 {
 177         char *vers;
 178 
 179         /* determine openssl version */
 180         vers = run_openssl(OP_VERSION, NULL);
 181         if (strstr(vers, "OpenSSL 1.") == vers)
 182                 newossl = 1;
 183         else if (strstr(vers, "OpenSSL 0.9.") != vers)
 184                 errx(SMF_EXIT_ERR_FATAL, _("unrecognised OpenSSL: %s"), vers);
 185         if (verbose)
 186                 printf(_(FMT_VERSION), vers, (newossl ? _(MSG_NEW_HASH) :
 187                     _(MSG_OLD_HASH)));
 188         free(vers);
 189 }
 190 
 191 void
 192 store_certlink(cert_link_t *cd)
 193 {
 194         if (link0 == NULL) {
 195                 link0 = cd;
 196         } else {
 197                 cert_link_t *t = link0;
 198                 while (t->next != NULL)
 199                         t = t->next;
 200                 t->next = cd;
 201         }
 202 }
 203 
 204 cert_link_t *
 205 find_link_by_hash(char *hash)
 206 {
 207         cert_link_t *t = link0;
 208         while (t != NULL) {
 209                 if (strcmp(t->hash, hash) == 0)
 210                         return (t);
 211                 t = t->next;
 212         }
 213         return (NULL);
 214 }
 215 
 216 void
 217 store_certfile(cert_file_t *cs)
 218 {
 219         if (file0 == NULL) {
 220                 file0 = cs;
 221         } else {
 222                 cert_file_t *t = file0;
 223                 while (t->next != NULL)
 224                         t = t->next;
 225                 t->next = cs;
 226         }
 227 }
 228 
 229 char *
 230 add_dot_zero(char *str)
 231 {
 232         char *ret;
 233         xasprintf(&ret, "%s.0", str);
 234         free(str);
 235         return (ret);
 236 }
 237 
 238 void
 239 populate_hash_list(cert_file_t *cs)
 240 {
 241         if (cs->path[0] != '/')
 242                 abort();
 243 
 244         if (newossl)
 245                 cs->hash[cs->hashcnt++] =
 246                     add_dot_zero(run_openssl(OP_HASH_OLD, cs->path));
 247         cs->hash[cs->hashcnt++] = add_dot_zero(run_openssl(OP_HASH, cs->path));
 248 }
 249 
 250 void
 251 get_cert_file_list(void)
 252 {
 253         DIR *dir;
 254         struct dirent *de;
 255 
 256         dir = opendir(certs_file_dir);
 257         if (dir == NULL)
 258                 err(SMF_EXIT_ERR_FATAL, _("could not read directory %s"),
 259                     certs_file_dir);
 260 
 261         for (de = readdir(dir); de != NULL; de = readdir(dir)) {
 262                 char *a;
 263                 cert_file_t *cs;
 264 
 265                 if (strcmp(".", de->d_name) == 0 ||
 266                     strcmp("..", de->d_name) == 0)
 267                         continue;
 268 
 269                 cs = xmalloc(sizeof (*cs));
 270                 xasprintf(&a, "%s/%s", certs_file_dir, de->d_name);
 271                 cs->path = xrealpath(a);
 272                 free(a);
 273                 populate_hash_list(cs);
 274                 store_certfile(cs);
 275         }
 276         (void) closedir(dir);
 277 }
 278 
 279 void
 280 removehashlink(char *hash)
 281 {
 282         char *fqhash;
 283         xasprintf(&fqhash, "%s/%s", certs_link_dir, hash);
 284         xunlink(fqhash);
 285         free(fqhash);
 286 }
 287 
 288 void
 289 fixhashlink(char *file, char *hash)
 290 {
 291         char *fqhash;
 292         char *relfile;
 293 
 294         xasprintf(&fqhash, "%s/%s", certs_link_dir, hash);
 295         relfile = xmakerelative(certs_link_dir, file);
 296 
 297         if (xexists(fqhash))
 298                 xunlink(fqhash);
 299 
 300         if (symlink(relfile, fqhash) != 0)
 301                 err(SMF_EXIT_ERR_FATAL, _("could not create symlink %s"),
 302                     fqhash);
 303 
 304         free(fqhash);
 305         free(relfile);
 306 }
 307 
 308 void
 309 get_cert_link_list(void)
 310 {
 311         DIR *dir;
 312         struct dirent *de;
 313 
 314         dir = opendir(certs_link_dir);
 315         if (dir == NULL)
 316                 err(SMF_EXIT_ERR_FATAL, _("could not read directory %s"),
 317                     certs_link_dir);
 318 
 319         for (de = readdir(dir); de != NULL; de = readdir(dir)) {
 320                 cert_link_t *cd;
 321                 char *a, *b;
 322 
 323                 if (strcmp(".", de->d_name) == 0 ||
 324                     strcmp("..", de->d_name) == 0)
 325                         continue;
 326 
 327                 cd = xmalloc(sizeof (*cd));
 328 
 329                 cd->hash = xstrdup(de->d_name);
 330 
 331                 xasprintf(&a, "%s/%s", certs_link_dir, de->d_name);
 332                 b = xreadlink(a);
 333                 free(a);
 334                 if (b[0] == '.') {
 335                         xasprintf(&a, "%s/%s", certs_link_dir, b);
 336                         free(b);
 337                         b = a;
 338                 }
 339                 a = xrealpath(b);
 340                 free(b);
 341                 cd->path = a;
 342 
 343                 store_certlink(cd);
 344         }
 345         (void) closedir(dir);
 346 }
 347 
 348 void
 349 read_config(void)
 350 {
 351         int a;
 352         char *b;
 353 
 354         a = get_config_boolean("verbose");
 355         if (a >= 0)
 356                 verbose = a;
 357         a = get_config_boolean("remove_dangling");
 358         if (a >= 0)
 359                 remove_dangling = a;
 360         a = get_config_boolean("remove_unknown");
 361         if (a >= 0)
 362                 remove_unknown = a;
 363         a = get_config_boolean("create_missing");
 364         if (a >= 0)
 365                 create_missing = a;
 366         a = get_config_boolean("correct_wrong");
 367         if (a >= 0)
 368                 correct_wrong = a;
 369 
 370         b = get_config_string("certs_file_dir");
 371         if (b != NULL)
 372                 certs_file_dir = b;
 373         b = get_config_string("certs_link_dir");
 374         if (b != NULL)
 375                 certs_link_dir = b;
 376         b = get_config_string("openssl_command");
 377         if (b != NULL)
 378                 openssl = b;
 379 }
 380 
 381 
 382 int
 383 main(void)
 384 {
 385         cert_file_t *tf;
 386         cert_link_t *tl;
 387 
 388         (void) setlocale(LC_ALL, "");
 389         (void) textdomain(TEXT_DOMAIN);
 390 
 391         if (init_scf() != 0)
 392                 errx(SMF_EXIT_ERR_FATAL, _("could not connect to smf(5): %s"),
 393                     scf_strerror(scf_error()));
 394         read_config();
 395         fini_scf();
 396 
 397 #ifdef  DEBUG
 398         verbose = B_TRUE;
 399 #endif
 400 
 401         detect_openssl();
 402 
 403         get_cert_link_list();
 404         get_cert_file_list();
 405 
 406         /*
 407          * look at each certificate file and determine if there is
 408          * a symlink to it in the link directory
 409          */
 410         for (tf = file0; tf != NULL; tf = tf->next) {
 411                 int j;
 412                 for (j = 0; j < tf->hashcnt; j++) {
 413                         tl = find_link_by_hash(tf->hash[j]);
 414                         if (tl) {
 415                                 tl->targetfound = 1;
 416                                 if (correct_wrong && strcmp(tl->path, tf->path)
 417                                     != 0) {
 418                                         if (verbose)
 419                                                 printf(_(FMT_WRONG),
 420                                                     tf->hash[j], tl->path,
 421                                                     tf->path);
 422                                         fixhashlink(tf->path, tf->hash[j]);
 423                                 }
 424                         } else if (create_missing) {
 425                                 if (verbose)
 426                                         printf(_(FMT_MISSING), tf->hash[j],
 427                                             tf->path);
 428                                 fixhashlink(tf->path, tf->hash[j]);
 429                         }
 430                 }
 431         }
 432         /*
 433          * look at each certificate link and determine if it's dangling or
 434          * pointing to a certificate file which we haven't seen
 435          */
 436         for (tl = link0; tl != NULL; tl = tl->next) {
 437                 if (!xexists(tl->path)) {
 438                         if (remove_dangling) {
 439                                 if (verbose)
 440                                         printf(_(FMT_DANGLING), tl->hash,
 441                                             tl->path);
 442                                 removehashlink(tl->hash);
 443                         }
 444                 } else if (!tl->targetfound) {
 445                         if (remove_unknown) {
 446                                 if (verbose)
 447                                         printf(_(FMT_UNKNOWN), tl->hash,
 448                                             tl->path);
 449                                 removehashlink(tl->hash);
 450                         }
 451                 }
 452         }
 453 
 454         return (0);
 455 }