diff options
-rw-r--r-- | rcynic/rcynic.c | 148 |
1 files changed, 112 insertions, 36 deletions
diff --git a/rcynic/rcynic.c b/rcynic/rcynic.c index 33d6b252..352907f1 100644 --- a/rcynic/rcynic.c +++ b/rcynic/rcynic.c @@ -19,6 +19,9 @@ /* * "Cynical rsync": Recursively walk RPKI tree using rsync to pull * data from remote sites, validating certificates and CRLs as we go. + * + * I'll probably end up breaking this up into multiple smaller files, + * but it's easiest to put everything in a single mongo file initially. */ #include <assert.h> @@ -35,6 +38,18 @@ #include <time.h> #include <string.h> #include <sys/stat.h> +#include <openssl/bio.h> +#include <openssl/pem.h> +#include <openssl/err.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/safestack.h> + +DECLARE_STACK_OF(char) + +static STACK_OF(char) *rsync_cache; + + /* * Logging functions. @@ -87,27 +102,78 @@ static void fatal(int retval, char *fmt, ...) exit(retval); } + + +/* + * Make a directory if it doesn't already exist. + */ + +static int mkdir_maybe(char *name) +{ + char *b, buffer[FILENAME_MAX]; + + if (!name || strlen(name) >= sizeof(buffer) - 2) + return 0; + strcpy(buffer, name); + strcat(buffer, "/."); + if (access(buffer, F_OK) == 0) + return 1; + if ((b = strrchr(strrchr(buffer, '/'), '/')) != 0) { + *b = '\0'; + if (!mkdir_maybe(buffer)) + return 0; + } + return mkdir(name, 0777) == 0; +} + +/* + * Convert an rsync URI to a filename, checking for evil character + * sequences. + */ + +static char *uri_to_filename(char *name) +{ + int n; + + if (!name || strncmp(name, "rsync://", sizeof("rsync://") - 1)) + return 0; + name += sizeof("rsync://") - 1; + + if (name[0] == '/' || name[0] == '.' || !strcmp(name, "../") || + strstr(name, "//") || strstr(name, "/../") || + ((n = strlen(name)) >= 3 && !strcmp(name + n - 3, "/.."))) + return 0; + + return strdup(name); +} + + + /* * Run rsync. * * This probably isn't paranoid enough. Should use select() to do * some kind of timeout when rsync is taking too long. Breaking the * log stream into lines without fgets() is a pain, maybe setting - * nonblocking I/O before calling fdopen() would suffice to let us - * use select()? If we can time out, we need to be able to kill() - * the rsync process. Might need to use waitpid() instead of wait() - * so we can specify the pid we want and WNOHANG. + * nonblocking I/O before calling fdopen() would suffice to let us use + * select()? If we time out, we need to kill() the rsync process. */ static char *rsync_cmd[] = { "rsync", "--update", "--times", "--copy-links", "--itemize-changes" }; +static int rsync_cmp(const char * const *a, const char * const *b) +{ + return strcmp(*a, *b); +} + static int rsync(char *args, ...) { - char *uri = 0, *argv[100], buffer[2000]; + char *uri = 0, *path, *s, *argv[100], buffer[2000]; int argc, pipe_fds[2], n, pid_status = -1; va_list ap; + pid_t pid; FILE *f; for (argc = 0; argc < sizeof(rsync_cmd)/sizeof(*rsync_cmd); argc++) @@ -122,17 +188,43 @@ static int rsync(char *args, ...) } va_end(ap); - /* - * This is where we'd check to see if we've already pulled this URI. - */ + if (!uri) { + logmsg("Couldn't extract URI from rsync command"); + return 0; + } - if (pipe(pipe_fds) < 0) - fatal(1, "pipe() failed"); + if ((path = uri_to_filename(uri)) == NULL) { + logmsg("Couldn't extract filename from URI for rsync cache"); + return 0; + } + + assert(rsync_cache != NULL); + if ((s = sk_value(rsync_cache, sk_find(rsync_cache, path))) != NULL && + !strncmp(s, path, strlen(s))) { + logmsg("Cache hit %s for URI %s, skipping rsync", s, uri); + free(path); + return 1; + } - switch (vfork()) { - case -1: - fatal(1, "vfork() failed"); - case 0: + if (pipe(pipe_fds) < 0) { + logmsg("pipe() failed"); + return 0; + } + + if ((f = fdopen(pipe_fds[0], "r")) == NULL) { + logmsg("Couldn't fdopen() rsync's output stream"); + close(pipe_fds[0]); + close(pipe_fds[1]); + return 0; + } + + switch ((pid = vfork())) { + case -1: + logmsg("vfork() failed"); + fclose(f); + close(pipe_fds[1]); + return 0; + case 0: close(pipe_fds[0]); if (dup2(pipe_fds[1], 1) < 0) fatal(-2, "dup2(1) failed"); @@ -144,9 +236,6 @@ static int rsync(char *args, ...) close(pipe_fds[1]); - if ((f = fdopen(pipe_fds[0], "r")) == NULL) - fatal(1, "Couldn't open rsync's output stream"); - while (fgets(buffer, sizeof(buffer), f)) { char *s = strchr(buffer, '\n'); if (s) @@ -154,7 +243,9 @@ static int rsync(char *args, ...) logmsg("%s", buffer); } - wait(&pid_status); + sk_push(rsync_cache, path); + + waitpid(pid, &pid_status, 0); if (WEXITSTATUS(pid_status)) { logmsg("rsync exited with status %d", pid_status); @@ -164,24 +255,9 @@ static int rsync(char *args, ...) } } + + /* - * Make a directory if it doesn't already exist. + * Dunno yet whether Perl parse_cert() has a C equivalent. */ -static int mkdir_maybe(char *name) -{ - char *b, buffer[FILENAME_MAX]; - - if (!name || strlen(name) >= sizeof(buffer) - 2) - return 0; - strcpy(buffer, name); - strcat(buffer, "/."); - if (access(buffer, F_OK) == 0) - return 1; - if ((b = strrchr(strrchr(buffer, '/'), '/')) != 0) { - *b = '\0'; - if (!mkdir_maybe(buffer)) - return 0; - } - return mkdir(name, 0777) == 0; -} |