#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #define CERTSDIR "/usr/share/ca-certificates/" #define LOCALCERTSDIR "/usr/local/share/ca-certificates/" #define ETCCERTSDIR "/etc/ssl/certs/" #define RUNPARTSDIR "/etc/ca-certificates/update.d/" #define CERTBUNDLE "ca-certificates.crt" #define CERTSCONF "/etc/ca-certificates.conf" static const char *last_component(const char *path) { const char *c = strrchr(path, '/'); if (c) return c + 1; return path; } static bool str_begins(const char* str, const char* prefix) { return !strncmp(str, prefix, strlen(prefix)); } struct hash_item { struct hash_item *next; char *key; char *value; }; struct hash { struct hash_item *items[256]; }; static unsigned int hash_string(const char *str) { unsigned long h = 5381; for (; *str; str++) h = (h << 5) + h + *str; return h; } static void hash_init(struct hash *h) { memset(h, 0, sizeof *h); } static struct hash_item *hash_get(struct hash *h, const char *key) { unsigned int bucket = hash_string(key) % ARRAY_SIZE(h->items); struct hash_item *item; for (item = h->items[bucket]; item; item = item->next) if (strcmp(item->key, key) == 0) return item; return NULL; } static void hash_foreach(struct hash *h, void (*cb)(struct hash_item *)) { struct hash_item *item; int i; for (i = 0; i < ARRAY_SIZE(h->items); i++) { for (item = h->items[i]; item; item = item->next) cb(item); } } static bool hash_add(struct hash *h, const char *key, const char *value) { unsigned int bucket = hash_string(key) % ARRAY_SIZE(h->items); size_t keylen = strlen(key), valuelen = strlen(value); struct hash_item *i; i = malloc(sizeof(struct hash_item) + keylen + 1 + valuelen + 1); if (!i) return false; i->key = (char*)(i+1); strcpy(i->key, key); i->value = i->key + keylen + 1; strcpy(i->value, value); i->next = h->items[bucket]; h->items[bucket] = i; return true; } static ssize_t buffered_copyfd(int in_fd, int out_fd, ssize_t in_size) { const size_t bufsize = 8192; char *buf = NULL; ssize_t r = 0, w = 0, copied = 0, n, m; if ((buf = malloc(bufsize)) == NULL) return -1; while (r < in_size && (n = read(in_fd, buf, bufsize))) { if (n == -1) { if (errno == EINTR) continue; break; } r = n; w = 0; while (w < r && (n = write(out_fd, buf + w, (r - w)))) { if (n == -1) { if (errno == EINTR) continue; break; } w += n; } copied += w; } free(buf); return copied; } static bool copyfile(const char* source, int output) { off_t bytes = 0; struct stat fileinfo = {0}; ssize_t result; int in_fd; if ((in_fd = open(source, O_RDONLY)) == -1) return false; if (fstat(in_fd, &fileinfo) < 0) { close(in_fd); return false; } result = sendfile(output, in_fd, &bytes, fileinfo.st_size); if ((result == -1) && (errno == EINVAL || errno == ENOSYS)) result = buffered_copyfd(in_fd, output, fileinfo.st_size); close(in_fd); return fileinfo.st_size == result; } typedef void (*proc_path)(const char *fullpath, struct hash *, int); static void proc_localglobaldir(const char *fullpath, struct hash *h, int tmpfile_fd) { const char *fname = last_component(fullpath); size_t flen = strlen(fname); char *s, *actual_file = NULL; /* Snip off the .crt suffix */ if (flen > 4 && strcmp(&fname[flen-4], ".crt") == 0) flen -= 4; if (asprintf(&actual_file, "%s%.*s%s", "ca-cert-", flen, fname, ".pem") == -1) { fprintf(stderr, "Cannot open path: %s\n", fullpath); return; } for (s = actual_file; *s; s++) { switch(*s) { case ',': case ' ': *s = '_'; break; case ')': case '(': *s = '='; break; default: break; } } if (!hash_add(h, actual_file, fullpath)) fprintf(stderr, "Warning! Cannot hash: %s\n", fullpath); if (!copyfile(fullpath, tmpfile_fd)) fprintf(stderr, "Warning! Cannot copy to bundle: %s\n", fullpath); write(tmpfile_fd, "\n", 1); free(actual_file); } static void proc_etccertsdir(const char* fullpath, struct hash* h, int tmpfile_fd) { char linktarget[PATH_MAX]; ssize_t linklen; linklen = readlink(fullpath, linktarget, sizeof(linktarget)-1); if (linklen < 0) return; linktarget[linklen] = 0; struct hash_item *item = hash_get(h, last_component(fullpath)); if (!item) { /* Symlink exists but is not wanted * Delete it if it points to 'our' directory */ if (str_begins(linktarget, CERTSDIR) || str_begins(linktarget, LOCALCERTSDIR)) unlink(fullpath); } else if (strcmp(linktarget, item->value) != 0) { /* Symlink exists but points wrong */ unlink(fullpath); if (symlink(item->value, fullpath) < 0) fprintf(stderr, "Warning! Cannot update symlink %s -> %s\n", item->value, fullpath); item->value = 0; } else { /* Symlink exists and is ok */ item->value = 0; } } static bool read_global_ca_list(const char* file, struct hash* d, int tmpfile_fd) { FILE * fp = fopen(file, "r"); if (fp == NULL) return false; char * line = NULL; size_t len = 0; ssize_t read; while ((read = getline(&line, &len, fp)) != -1) { /* getline returns number of bytes in buffer, and buffer * contains delimeter if it was found */ if (read > 0 && line[read-1] == '\n') line[read-1] = 0; if (str_begins(line, "#") || str_begins(line, "!")) continue; char* fullpath = 0; if (asprintf(&fullpath,"%s%s", CERTSDIR, line) != -1) { proc_localglobaldir(fullpath, d, tmpfile_fd); free(fullpath); } } fclose(fp); free(line); return true; } static bool dir_readfiles(struct hash* d, const char* path, proc_path path_processor, int tmpfile_fd) { DIR *dp = opendir(path); if (!dp) return false; struct dirent *dirp; while ((dirp = readdir(dp)) != NULL) { if (str_begins(dirp->d_name, ".")) continue; char* fullpath = 0; if (asprintf(&fullpath, "%s%s", path, dirp->d_name) != -1) { path_processor(fullpath, d, tmpfile_fd); free(fullpath); } } return closedir(dp) == 0; } static void update_ca_symlink(struct hash_item *item) { if (!item->value) return; char* newpath = 0; bool build_str = asprintf(&newpath, "%s%s", ETCCERTSDIR, item->key); if (!build_str || symlink(item->value, newpath) == -1) fprintf(stderr, "Warning! Cannot symlink %s -> %s\n", item->value, newpath); free(newpath); } int main(int a, char **v) { struct hash _calinks, *calinks = &_calinks; const char* bundle = "bundleXXXXXX"; char* tmpfile = 0; if (asprintf(&tmpfile, "%s%s", ETCCERTSDIR, bundle) == -1) return 1; int fd = mkstemp(tmpfile); if (fd == -1) { fprintf(stderr, "Failed to open temporary file %s for ca bundle\n", tmpfile); return 1; } fchmod(fd, 0644); hash_init(calinks); /* Handle global CA certs from config file */ read_global_ca_list(CERTSCONF, calinks, fd); /* Handle local CA certificates */ dir_readfiles(calinks, LOCALCERTSDIR, &proc_localglobaldir, fd); /* Update etc cert dir for additions and deletions*/ dir_readfiles(calinks, ETCCERTSDIR, &proc_etccertsdir, fd); hash_foreach(calinks, update_ca_symlink); /* Update hashes and the bundle */ if (fd != -1) { close(fd); char* newcertname = 0; if (asprintf(&newcertname, "%s%s", ETCCERTSDIR, CERTBUNDLE) != -1) { rename(tmpfile, newcertname); free(newcertname); } } free(tmpfile); /* Execute run-parts */ static char *const run_parts_args[] = { "run-parts", RUNPARTSDIR, 0 }; execve("/usr/bin/run-parts", run_parts_args, NULL); execve("/bin/run-parts", run_parts_args, NULL); perror("run-parts"); return 1; }