aboutsummaryrefslogtreecommitdiff
path: root/rcynic-ng/rcynic.c
diff options
context:
space:
mode:
authorRob Austein <sra@hactrn.net>2011-09-14 04:10:32 +0000
committerRob Austein <sra@hactrn.net>2011-09-14 04:10:32 +0000
commiteade3469864319c78ac655884f1e47dc3803e5a5 (patch)
tree9f17921a790fd1aa404f92d72060413628700849 /rcynic-ng/rcynic.c
parentcec922796bb865dd12a223ef7966e30622d1bb28 (diff)
Rework output directory scheme to do atomic instalation of new results
via the ancient rename-symlink-to-directory hack. (#61) svn path=/rcynic-ng/rcynic.c; revision=3980
Diffstat (limited to 'rcynic-ng/rcynic.c')
-rw-r--r--rcynic-ng/rcynic.c203
1 files changed, 168 insertions, 35 deletions
diff --git a/rcynic-ng/rcynic.c b/rcynic-ng/rcynic.c
index 6aa90a52..8eac4768 100644
--- a/rcynic-ng/rcynic.c
+++ b/rcynic-ng/rcynic.c
@@ -60,6 +60,8 @@
#include <fcntl.h>
#include <signal.h>
#include <utime.h>
+#include <glob.h>
+#include <sys/param.h>
#define SYSLOG_NAMES /* defines CODE prioritynames[], facilitynames[] */
#include <syslog.h>
@@ -80,7 +82,9 @@
#include "defstack.h"
#include "defasn1.h"
-#ifndef FILENAME_MAX
+#if !defined(FILENAME_MAX) && defined(PATH_MAX) && PATH_MAX > 1024
+#define FILENAME_MAX PATH_MAX
+#elif !defined(FILENAME_MAX)
#define FILENAME_MAX 1024
#endif
@@ -462,7 +466,7 @@ typedef struct rcynic_x509_store_ctx {
* Program context that would otherwise be a mess of global variables.
*/
struct rcynic_ctx {
- path_t authenticated, old_authenticated, unauthenticated;
+ path_t authenticated, old_authenticated, new_authenticated, unauthenticated;
char *jane, *rsync_program;
STACK_OF(OPENSSL_STRING) *rsync_cache, *backup_cache, *dead_host_cache;
STACK_OF(validation_status_t) *validation_status;
@@ -520,6 +524,14 @@ static const unsigned char id_sha256[] =
*/
static const char rpki_policy_oid[] = "1.3.6.1.5.5.7.14.2";
+/**
+ * Suffix we use temporarily during the symlink shuffle. Could be
+ * almost anything, but we want to do the length check early, before
+ * we waste a lot of work we'll just have to throw away, so we just
+ * wire in something short and obvious.
+ */
+static const char authenticated_symlink_suffix[] = ".new";
+
/**
@@ -1018,7 +1030,7 @@ static int install_object(const rcynic_ctx_t *rc,
{
path_t target;
- if (!uri_to_filename(rc, uri, &target, &rc->authenticated)) {
+ if (!uri_to_filename(rc, uri, &target, &rc->new_authenticated)) {
logmsg(rc, log_data_err, "Couldn't generate installation name for %s", uri->s);
return 0;
}
@@ -1060,18 +1072,25 @@ static int startswith(const char *str, const char *prefix)
/**
- * Set a directory name, making sure it has the trailing slash we
- * require in various other routines.
+ * Set a directory name, adding or stripping trailing slash as needed.
*/
-static int set_directory(const rcynic_ctx_t *rc, path_t *out, const char *in)
+static int set_directory(const rcynic_ctx_t *rc, path_t *out, const char *in, const int want_slash)
{
- int need_slash;
+ int has_slash, need_slash;
size_t n;
assert(rc && in && out);
+
n = strlen(in);
- assert(n > 0);
- need_slash = in[n - 1] != '/';
+
+ if (n == 0) {
+ logmsg(rc, log_usage_err, "Empty path");
+ return 0;
+ }
+
+ has_slash = in[n - 1] == '/';
+
+ need_slash = want_slash && !has_slash;
if (n + need_slash + 1 > sizeof(out->s)) {
logmsg(rc, log_usage_err, "Path \"%s\" too long", in);
@@ -1081,6 +1100,42 @@ static int set_directory(const rcynic_ctx_t *rc, path_t *out, const char *in)
strcpy(out->s, in);
if (need_slash)
strcat(out->s, "/");
+ else if (has_slash && !want_slash)
+ out->s[n - 1] = '\0';
+
+ return 1;
+}
+
+/**
+ * Construct names for the directories not directly settable by the
+ * user.
+ */
+static int construct_directory_names(rcynic_ctx_t *rc)
+{
+ ssize_t n;
+ path_t p;
+ time_t t = time(0);
+
+ p = rc->authenticated;
+
+ n = strlen(p.s);
+
+ if (n + sizeof(authenticated_symlink_suffix) >= sizeof(p.s)) {
+ logmsg(rc, log_usage_err, "Symlink name would be too long");
+ return 0;
+ }
+
+ if (strftime(p.s + n, sizeof(p.s) - n - 1, ".%Y-%m-%dT%H:%M:%SZ", gmtime(&t)) == 0) {
+ logmsg(rc, log_usage_err, "Generated path with timestamp would be too long");
+ return 0;
+ }
+
+ if (!set_directory(rc, &rc->new_authenticated, p.s, 1))
+ return 0;
+
+ if (!set_directory(rc, &rc->old_authenticated, rc->authenticated.s, 1))
+ return 0;
+
return 1;
}
@@ -1143,6 +1198,88 @@ static int rm_rf(const path_t *name)
return ret;
}
+/**
+ * Do final symlink shuffle and cleanup of output directories.
+ */
+static int finalize_directories(const rcynic_ctx_t *rc)
+{
+ path_t path, sym, real_old, real_new;
+ const char *dir;
+ size_t n;
+ glob_t g;
+ int i;
+
+ if (!realpath(rc->old_authenticated.s, real_old.s))
+ real_old.s[0] = '\0';
+
+ if (!realpath(rc->new_authenticated.s, real_new.s))
+ real_old.s[0] = '\0';
+
+ path = rc->new_authenticated;
+
+ n = strlen(path.s);
+ assert(n > 1 && path.s[n - 1] == '/');
+ path.s[n - 1] = '\0';
+
+ if ((dir = strrchr(path.s, '/')) == NULL)
+ dir = path.s;
+ else
+ dir++;
+
+ sym = rc->authenticated;
+
+ assert(strlen(sym.s) + sizeof(authenticated_symlink_suffix) < sizeof(sym.s));
+ strcat(sym.s, authenticated_symlink_suffix);
+
+ (void) unlink(sym.s);
+
+ if (symlink(dir, sym.s) < 0) {
+ logmsg(rc, log_sys_err, "Couldn't link %s to %s: %s",
+ sym.s, dir, strerror(errno));
+ return 0;
+ }
+
+ if (rename(sym.s, rc->authenticated.s) < 0) {
+ logmsg(rc, log_sys_err, "Couldn't rename %s to %s: %s",
+ sym.s, rc->authenticated.s, strerror(errno));
+ return 0;
+ }
+
+ path = rc->authenticated;
+ assert(strlen(path.s) + sizeof(".*") < sizeof(path.s));
+ strcat(path.s, ".*");
+
+ memset(&g, 0, sizeof(g));
+
+ if (real_new.s[0] && glob(path.s, 0, 0, &g) == 0)
+ for (i = 0; i < g.gl_pathc; i++)
+ if (realpath(g.gl_pathv[i], path.s) &&
+ strcmp(path.s, real_old.s) &&
+ strcmp(path.s, real_new.s))
+ rm_rf(&path);
+
+ return 1;
+}
+
+/**
+ * Be kind to people who are upgrading: tell them what's wrong when we
+ * start up, rather than doing all the work then throwing away
+ * results. Some day this code will go away.
+ */
+static int upgraded_from_pre_symlink_rcynic(const rcynic_ctx_t *rc)
+{
+ path_t p;
+
+ if (readlink(rc->authenticated.s, p.s, sizeof(p.s)) == 0 && errno == EINVAL) {
+ logmsg(rc, log_usage_err,
+ "You appear to be upgrading from an old version of rcynic. "
+ "Please remove %s then run rcynic again.", rc->authenticated.s);
+ return 0;
+ }
+
+ return 1;
+}
+
/**
@@ -2552,7 +2689,7 @@ static X509_CRL *check_crl(const rcynic_ctx_t *rc,
path_t path;
X509_CRL *crl;
- if (uri_to_filename(rc, uri, &path, &rc->authenticated) &&
+ if (uri_to_filename(rc, uri, &path, &rc->new_authenticated) &&
(crl = read_crl(&path, NULL)) != NULL)
return crl;
@@ -2971,7 +3108,7 @@ static X509 *check_cert(rcynic_ctx_t *rc,
* better data, just get out now.
*/
- if (uri_to_filename(rc, uri, &path, &rc->authenticated) &&
+ if (uri_to_filename(rc, uri, &path, &rc->new_authenticated) &&
!access(path.s, R_OK)) {
if (w->state == walk_state_backup || sk_OPENSSL_STRING_find(rc->backup_cache, uri->s) < 0)
return NULL;
@@ -3176,7 +3313,7 @@ static Manifest *check_manifest(const rcynic_ctx_t *rc,
uri = &w->certinfo.manifest;
- if (uri_to_filename(rc, uri, &path, &rc->authenticated) &&
+ if (uri_to_filename(rc, uri, &path, &rc->new_authenticated) &&
(cms = read_cms(&path, NULL)) != NULL &&
(bio = BIO_new(BIO_s_mem()))!= NULL &&
CMS_verify(cms, NULL, NULL, NULL, bio,
@@ -3495,7 +3632,7 @@ static void check_roa(const rcynic_ctx_t *rc,
assert(rc && uri && wsk);
- if (uri_to_filename(rc, uri, &path, &rc->authenticated) &&
+ if (uri_to_filename(rc, uri, &path, &rc->new_authenticated) &&
!access(path.s, F_OK))
return;
@@ -3669,7 +3806,7 @@ static void check_ghostbuster(const rcynic_ctx_t *rc,
assert(rc && uri && wsk);
- if (uri_to_filename(rc, uri, &path, &rc->authenticated) &&
+ if (uri_to_filename(rc, uri, &path, &rc->new_authenticated) &&
!access(path.s, F_OK))
return;
@@ -3977,9 +4114,8 @@ int main(int argc, char *argv[])
LOG_LEVELS;
#undef QQ
- if (!set_directory(&rc, &rc.authenticated, "rcynic-data/authenticated/") ||
- !set_directory(&rc, &rc.old_authenticated, "rcynic-data/authenticated.old/") ||
- !set_directory(&rc, &rc.unauthenticated, "rcynic-data/unauthenticated/"))
+ if (!set_directory(&rc, &rc.authenticated, "rcynic-data/authenticated", 0) ||
+ !set_directory(&rc, &rc.unauthenticated, "rcynic-data/unauthenticated/", 1))
goto done;
OpenSSL_add_all_algorithms();
@@ -4047,15 +4183,11 @@ int main(int argc, char *argv[])
assert(val && val->name && val->value);
if (!name_cmp(val->name, "authenticated") &&
- !set_directory(&rc, &rc.authenticated, val->value))
- goto done;
-
- else if (!name_cmp(val->name, "old-authenticated") &&
- !set_directory(&rc, &rc.old_authenticated, val->value))
+ !set_directory(&rc, &rc.authenticated, val->value, 0))
goto done;
else if (!name_cmp(val->name, "unauthenticated") &&
- !set_directory(&rc, &rc.unauthenticated, val->value))
+ !set_directory(&rc, &rc.unauthenticated, val->value, 1))
goto done;
else if (!name_cmp(val->name, "rsync-timeout") &&
@@ -4192,6 +4324,9 @@ int main(int argc, char *argv[])
LOG_PID | (use_stderr ? LOG_PERROR : 0),
(syslog_facility ? syslog_facility : LOG_LOCAL0));
+ if (!upgraded_from_pre_symlink_rcynic(&rc))
+ goto done;
+
if (jitter > 0) {
if (RAND_bytes((unsigned char *) &delay, sizeof(delay)) <= 0) {
logmsg(&rc, log_sys_err, "Couldn't read random bytes");
@@ -4217,22 +4352,17 @@ int main(int argc, char *argv[])
start = time(0);
logmsg(&rc, log_telemetry, "Starting");
- if (!rm_rf(&rc.old_authenticated)) {
- logmsg(&rc, log_sys_err, "Couldn't remove %s: %s",
- rc.old_authenticated.s, strerror(errno));
+ if (!construct_directory_names(&rc))
goto done;
- }
- if (rename(rc.authenticated.s, rc.old_authenticated.s) < 0 &&
- errno != ENOENT) {
- logmsg(&rc, log_sys_err, "Couldn't rename %s to %s: %s",
- rc.old_authenticated.s, rc.authenticated.s, strerror(errno));
+ if (!access(rc.new_authenticated.s, F_OK)) {
+ logmsg(&rc, log_sys_err, "Timestamped output directory %s already exists! Clock went backwards?", rc.new_authenticated.s);
goto done;
}
- if (!access(rc.authenticated.s, F_OK) || !mkdir_maybe(&rc, &rc.authenticated)) {
+ if (!mkdir_maybe(&rc, &rc.new_authenticated)) {
logmsg(&rc, log_sys_err, "Couldn't prepare directory %s: %s",
- rc.authenticated.s, strerror(errno));
+ rc.new_authenticated.s, strerror(errno));
goto done;
}
@@ -4303,7 +4433,7 @@ int main(int argc, char *argv[])
hash = X509_subject_name_hash(x);
for (j = 0; j < INT_MAX; j++) {
if (snprintf(path2.s, sizeof(path2.s), "%s%lx.%d.cer",
- rc.authenticated.s, hash, j) == sizeof(path2.s)) {
+ rc.new_authenticated.s, hash, j) == sizeof(path2.s)) {
logmsg(&rc, log_sys_err,
"Couldn't construct path name for trust anchor %s", path1.s);
goto done;
@@ -4337,7 +4467,7 @@ int main(int argc, char *argv[])
bio = BIO_push(BIO_new(BIO_f_linebreak()), bio);
bio = BIO_push(BIO_new(BIO_f_base64()), bio);
if (!uri_to_filename(&rc, &uri, &path1, &rc.unauthenticated) ||
- !uri_to_filename(&rc, &uri, &path2, &rc.authenticated) ||
+ !uri_to_filename(&rc, &uri, &path2, &rc.new_authenticated) ||
!uri_to_filename(&rc, &uri, &path3, &rc.old_authenticated)) {
log_validation_status(&rc, &uri, unreadable_trust_anchor_locator, object_generation_null);
BIO_free_all(bio);
@@ -4392,6 +4522,9 @@ int main(int argc, char *argv[])
wsk = NULL; /* Ownership of wsk passed to check_ta() */
}
+ if (!finalize_directories(&rc))
+ goto done;
+
if (prune && !prune_unauthenticated(&rc, &rc.unauthenticated,
strlen(rc.unauthenticated.s))) {
logmsg(&rc, log_sys_err, "Trouble pruning old unauthenticated data");