aboutsummaryrefslogtreecommitdiff
path: root/src/kprop/kprop.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/kprop/kprop.c')
-rw-r--r--src/kprop/kprop.c597
1 files changed, 597 insertions, 0 deletions
diff --git a/src/kprop/kprop.c b/src/kprop/kprop.c
new file mode 100644
index 0000000..b7fb637
--- /dev/null
+++ b/src/kprop/kprop.c
@@ -0,0 +1,597 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* kprop/kprop.c */
+/*
+ * Copyright 1990,1991,2008 by the Massachusetts Institute of Technology.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+#include <locale.h>
+#include <sys/file.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/param.h>
+#include <netdb.h>
+#include <fcntl.h>
+
+#include "com_err.h"
+#include "fake-addrinfo.h"
+#include "kprop.h"
+
+#ifndef GETSOCKNAME_ARG3_TYPE
+#define GETSOCKNAME_ARG3_TYPE unsigned int
+#endif
+
+static char *kprop_version = KPROP_PROT_VERSION;
+
+static char *progname = NULL;
+static int debug = 0;
+static char *srvtab = NULL;
+static char *replica_host;
+static char *realm = NULL;
+static char *def_realm = NULL;
+static char *file = KPROP_DEFAULT_FILE;
+
+/* The Kerberos principal we'll be sending as, initialized in get_tickets. */
+static krb5_principal my_principal;
+
+static krb5_creds creds;
+static krb5_address *sender_addr;
+static krb5_address *receiver_addr;
+static const char *port = KPROP_SERVICE;
+static char *dbpathname;
+
+static void parse_args(krb5_context context, int argc, char **argv);
+static void get_tickets(krb5_context context);
+static void usage(void);
+static void open_connection(krb5_context context, char *host, int *fd_out);
+static void kerberos_authenticate(krb5_context context,
+ krb5_auth_context *auth_context, int fd,
+ krb5_principal me, krb5_creds **new_creds);
+static int open_database(krb5_context context, char *data_fn, int *size);
+static void close_database(krb5_context context, int fd);
+static void xmit_database(krb5_context context,
+ krb5_auth_context auth_context, krb5_creds *my_creds,
+ int fd, int database_fd, int in_database_size);
+static void send_error(krb5_context context, krb5_creds *my_creds, int fd,
+ char *err_text, krb5_error_code err_code);
+static void update_last_prop_file(char *hostname, char *file_name);
+
+static void usage()
+{
+ fprintf(stderr, _("\nUsage: %s [-r realm] [-f file] [-d] [-P port] "
+ "[-s srvtab] replica_host\n\n"), progname);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ int fd, database_fd, database_size;
+ krb5_error_code retval;
+ krb5_context context;
+ krb5_creds *my_creds;
+ krb5_auth_context auth_context;
+
+ setlocale(LC_ALL, "");
+ retval = krb5_init_context(&context);
+ if (retval) {
+ com_err(argv[0], retval, _("while initializing krb5"));
+ exit(1);
+ }
+ parse_args(context, argc, argv);
+ get_tickets(context);
+
+ database_fd = open_database(context, file, &database_size);
+ open_connection(context, replica_host, &fd);
+ kerberos_authenticate(context, &auth_context, fd, my_principal, &my_creds);
+ xmit_database(context, auth_context, my_creds, fd, database_fd,
+ database_size);
+ update_last_prop_file(replica_host, file);
+ printf(_("Database propagation to %s: SUCCEEDED\n"), replica_host);
+ krb5_free_cred_contents(context, my_creds);
+ close_database(context, database_fd);
+ krb5_free_default_realm(context, def_realm);
+ exit(0);
+}
+
+static void
+parse_args(krb5_context context, int argc, char **argv)
+{
+ int c;
+ krb5_error_code ret;
+
+ progname = argv[0];
+ while ((c = getopt(argc, argv, "r:f:dP:s:")) != -1) {
+ switch (c) {
+ case 'r':
+ realm = optarg;
+ break;
+ case 'f':
+ file = optarg;
+ break;
+ case 'd':
+ debug++;
+ break;
+ case 'P':
+ port = optarg;
+ break;
+ case 's':
+ srvtab = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+ if (argc - optind != 1)
+ usage();
+ replica_host = argv[optind];
+
+ if (realm == NULL) {
+ ret = krb5_get_default_realm(context, &def_realm);
+ if (ret) {
+ com_err(progname, errno, _("while getting default realm"));
+ exit(1);
+ }
+ realm = def_realm;
+ }
+}
+
+static void
+get_tickets(krb5_context context)
+{
+ char *server;
+ krb5_error_code retval;
+ krb5_keytab keytab = NULL;
+ krb5_principal server_princ = NULL;
+
+ /* Figure out what tickets we'll be using to send. */
+ retval = sn2princ_realm(context, NULL, KPROP_SERVICE_NAME, realm,
+ &my_principal);
+ if (retval) {
+ com_err(progname, errno, _("while setting client principal name"));
+ exit(1);
+ }
+
+ /* Construct the principal name for the replica host. */
+ memset(&creds, 0, sizeof(creds));
+ retval = sn2princ_realm(context, replica_host, KPROP_SERVICE_NAME, realm,
+ &server_princ);
+ if (retval) {
+ com_err(progname, errno, _("while setting server principal name"));
+ exit(1);
+ }
+ retval = krb5_unparse_name_flags(context, server_princ,
+ KRB5_PRINCIPAL_UNPARSE_NO_REALM, &server);
+ if (retval) {
+ com_err(progname, retval, _("while unparsing server name"));
+ exit(1);
+ }
+
+ if (srvtab != NULL) {
+ retval = krb5_kt_resolve(context, srvtab, &keytab);
+ if (retval) {
+ com_err(progname, retval, _("while resolving keytab"));
+ exit(1);
+ }
+ }
+
+ retval = krb5_get_init_creds_keytab(context, &creds, my_principal, keytab,
+ 0, server, NULL);
+ if (retval) {
+ com_err(progname, retval, _("while getting initial credentials\n"));
+ exit(1);
+ }
+
+ if (keytab != NULL)
+ krb5_kt_close(context, keytab);
+ krb5_free_unparsed_name(context, server);
+ krb5_free_principal(context, server_princ);
+}
+
+static void
+open_connection(krb5_context context, char *host, int *fd_out)
+{
+ krb5_error_code retval;
+ GETSOCKNAME_ARG3_TYPE socket_length;
+ struct addrinfo hints, *res, *answers;
+ struct sockaddr *sa;
+ struct sockaddr_storage my_sin;
+ int s, error;
+
+ *fd_out = -1;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_ADDRCONFIG;
+ error = getaddrinfo(host, port, &hints, &answers);
+ if (error != 0) {
+ com_err(progname, 0, "%s: %s", host, gai_strerror(error));
+ exit(1);
+ }
+
+ s = -1;
+ retval = EINVAL;
+ for (res = answers; res != NULL; res = res->ai_next) {
+ s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (s < 0) {
+ com_err(progname, errno, _("while creating socket"));
+ exit(1);
+ }
+
+ if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
+ retval = errno;
+ close(s);
+ s = -1;
+ continue;
+ }
+
+ /* We successfully connect()ed */
+ *fd_out = s;
+ retval = sockaddr2krbaddr(context, res->ai_family, res->ai_addr,
+ &receiver_addr);
+ if (retval != 0) {
+ com_err(progname, retval, _("while converting server address"));
+ exit(1);
+ }
+
+ break;
+ }
+
+ freeaddrinfo(answers);
+
+ if (s == -1) {
+ com_err(progname, retval, _("while connecting to server"));
+ exit(1);
+ }
+
+ /* Set sender_addr. */
+ socket_length = sizeof(my_sin);
+ if (getsockname(s, (struct sockaddr *)&my_sin, &socket_length) < 0) {
+ com_err(progname, errno, _("while getting local socket address"));
+ exit(1);
+ }
+ sa = (struct sockaddr *)&my_sin;
+ if (sockaddr2krbaddr(context, sa->sa_family, sa, &sender_addr) != 0) {
+ com_err(progname, errno, _("while converting local address"));
+ exit(1);
+ }
+}
+
+static void
+kerberos_authenticate(krb5_context context, krb5_auth_context *auth_context,
+ int fd, krb5_principal me, krb5_creds **new_creds)
+{
+ krb5_error_code retval;
+ krb5_error *error = NULL;
+ krb5_ap_rep_enc_part *rep_result;
+
+ retval = krb5_auth_con_init(context, auth_context);
+ if (retval)
+ exit(1);
+
+ krb5_auth_con_setflags(context, *auth_context,
+ KRB5_AUTH_CONTEXT_DO_SEQUENCE);
+
+ retval = krb5_auth_con_setaddrs(context, *auth_context, sender_addr,
+ receiver_addr);
+ if (retval) {
+ com_err(progname, retval, _("in krb5_auth_con_setaddrs"));
+ exit(1);
+ }
+
+ retval = krb5_sendauth(context, auth_context, &fd, kprop_version,
+ me, creds.server, AP_OPTS_MUTUAL_REQUIRED, NULL,
+ &creds, NULL, &error, &rep_result, new_creds);
+ if (retval) {
+ com_err(progname, retval, _("while authenticating to server"));
+ if (error != NULL) {
+ if (error->error == KRB_ERR_GENERIC) {
+ if (error->text.data) {
+ fprintf(stderr, _("Generic remote error: %s\n"),
+ error->text.data);
+ }
+ } else if (error->error) {
+ com_err(progname,
+ (krb5_error_code)error->error + ERROR_TABLE_BASE_krb5,
+ _("signalled from server"));
+ if (error->text.data) {
+ fprintf(stderr, _("Error text from server: %s\n"),
+ error->text.data);
+ }
+ }
+ krb5_free_error(context, error);
+ }
+ exit(1);
+ }
+ krb5_free_ap_rep_enc_part(context, rep_result);
+}
+
+/*
+ * Open the Kerberos database dump file. Takes care of locking it
+ * and making sure that the .ok file is more recent that the database
+ * dump file itself.
+ *
+ * Returns the file descriptor of the database dump file. Also fills
+ * in the size of the database file.
+ */
+static int
+open_database(krb5_context context, char *data_fn, int *size)
+{
+ struct stat stbuf, stbuf_ok;
+ char *data_ok_fn;
+ int fd, err;
+
+ dbpathname = strdup(data_fn);
+ if (dbpathname == NULL) {
+ com_err(progname, ENOMEM, _("allocating database file name '%s'"),
+ data_fn);
+ exit(1);
+ }
+ fd = open(dbpathname, O_RDONLY);
+ if (fd < 0) {
+ com_err(progname, errno, _("while trying to open %s"), dbpathname);
+ exit(1);
+ }
+
+ err = krb5_lock_file(context, fd,
+ KRB5_LOCKMODE_SHARED | KRB5_LOCKMODE_DONTBLOCK);
+ if (err == EAGAIN || err == EWOULDBLOCK || errno == EACCES) {
+ com_err(progname, 0, _("database locked"));
+ exit(1);
+ } else if (err) {
+ com_err(progname, err, _("while trying to lock '%s'"), dbpathname);
+ exit(1);
+ }
+ if (fstat(fd, &stbuf)) {
+ com_err(progname, errno, _("while trying to stat %s"), data_fn);
+ exit(1);
+ }
+ if (asprintf(&data_ok_fn, "%s.dump_ok", data_fn) < 0) {
+ com_err(progname, ENOMEM, _("while trying to malloc data_ok_fn"));
+ exit(1);
+ }
+ if (stat(data_ok_fn, &stbuf_ok)) {
+ com_err(progname, errno, _("while trying to stat %s"), data_ok_fn);
+ free(data_ok_fn);
+ exit(1);
+ }
+ if (stbuf.st_mtime > stbuf_ok.st_mtime) {
+ com_err(progname, 0, _("'%s' more recent than '%s'."), data_fn,
+ data_ok_fn);
+ exit(1);
+ }
+ free(data_ok_fn);
+ *size = stbuf.st_size;
+ return fd;
+}
+
+static void
+close_database(krb5_context context, int fd)
+{
+ int err;
+
+ err = krb5_lock_file(context, fd, KRB5_LOCKMODE_UNLOCK);
+ if (err)
+ com_err(progname, err, _("while unlocking database '%s'"), dbpathname);
+ free(dbpathname);
+ close(fd);
+}
+
+/*
+ * Now we send over the database. We use the following protocol:
+ * Send over a KRB_SAFE message with the size. Then we send over the
+ * database in blocks of KPROP_BLKSIZE, encrypted using KRB_PRIV.
+ * Then we expect to see a KRB_SAFE message with the size sent back.
+ *
+ * At any point in the protocol, we may send a KRB_ERROR message; this
+ * will abort the entire operation.
+ */
+static void
+xmit_database(krb5_context context, krb5_auth_context auth_context,
+ krb5_creds *my_creds, int fd, int database_fd,
+ int in_database_size)
+{
+ krb5_int32 n;
+ krb5_data inbuf, outbuf;
+ char buf[KPROP_BUFSIZ];
+ krb5_error_code retval;
+ krb5_error *error;
+ krb5_ui_4 database_size = in_database_size, send_size, sent_size;
+
+ /* Send over the size. */
+ send_size = htonl(database_size);
+ inbuf.data = (char *)&send_size;
+ inbuf.length = sizeof(send_size); /* must be 4, really */
+ /* KPROP_CKSUMTYPE */
+ retval = krb5_mk_safe(context, auth_context, &inbuf, &outbuf, NULL);
+ if (retval) {
+ com_err(progname, retval, _("while encoding database size"));
+ send_error(context, my_creds, fd, _("while encoding database size"),
+ retval);
+ exit(1);
+ }
+
+ retval = krb5_write_message(context, &fd, &outbuf);
+ if (retval) {
+ krb5_free_data_contents(context, &outbuf);
+ com_err(progname, retval, _("while sending database size"));
+ exit(1);
+ }
+ krb5_free_data_contents(context, &outbuf);
+
+ /* Initialize the initial vector. */
+ retval = krb5_auth_con_initivector(context, auth_context);
+ if (retval) {
+ send_error(context, my_creds, fd,
+ "failed while initializing i_vector", retval);
+ com_err(progname, retval, _("while allocating i_vector"));
+ exit(1);
+ }
+
+ /* Send over the file, block by block. */
+ inbuf.data = buf;
+ sent_size = 0;
+ while ((n = read(database_fd, buf, sizeof(buf)))) {
+ inbuf.length = n;
+ retval = krb5_mk_priv(context, auth_context, &inbuf, &outbuf, NULL);
+ if (retval) {
+ snprintf(buf, sizeof(buf),
+ "while encoding database block starting at %d",
+ sent_size);
+ com_err(progname, retval, "%s", buf);
+ send_error(context, my_creds, fd, buf, retval);
+ exit(1);
+ }
+
+ retval = krb5_write_message(context, &fd, &outbuf);
+ if (retval) {
+ krb5_free_data_contents(context, &outbuf);
+ com_err(progname, retval,
+ _("while sending database block starting at %d"),
+ sent_size);
+ exit(1);
+ }
+ krb5_free_data_contents(context, &outbuf);
+ sent_size += n;
+ if (debug)
+ printf("%d bytes sent.\n", sent_size);
+ }
+ if (sent_size != database_size) {
+ com_err(progname, 0, _("Premature EOF found for database file!"));
+ send_error(context, my_creds, fd,
+ "Premature EOF found for database file!",
+ KRB5KRB_ERR_GENERIC);
+ exit(1);
+ }
+
+ /*
+ * OK, we've sent the database; now let's wait for a success
+ * indication from the remote end.
+ */
+ retval = krb5_read_message(context, &fd, &inbuf);
+ if (retval) {
+ com_err(progname, retval, _("while reading response from server"));
+ exit(1);
+ }
+ /*
+ * If we got an error response back from the server, display
+ * the error message
+ */
+ if (krb5_is_krb_error(&inbuf)) {
+ retval = krb5_rd_error(context, &inbuf, &error);
+ if (retval) {
+ com_err(progname, retval,
+ _("while decoding error response from server"));
+ exit(1);
+ }
+ if (error->error == KRB_ERR_GENERIC) {
+ if (error->text.data) {
+ fprintf(stderr, _("Generic remote error: %s\n"),
+ error->text.data);
+ }
+ } else if (error->error) {
+ com_err(progname,
+ (krb5_error_code)error->error + ERROR_TABLE_BASE_krb5,
+ _("signalled from server"));
+ if (error->text.data) {
+ fprintf(stderr, _("Error text from server: %s\n"),
+ error->text.data);
+ }
+ }
+ krb5_free_error(context, error);
+ exit(1);
+ }
+
+ retval = krb5_rd_safe(context,auth_context,&inbuf,&outbuf,NULL);
+ if (retval) {
+ com_err(progname, retval,
+ "while decoding final size packet from server");
+ exit(1);
+ }
+
+ memcpy(&send_size, outbuf.data, sizeof(send_size));
+ send_size = ntohl(send_size);
+ if (send_size != database_size) {
+ com_err(progname, 0, _("Kpropd sent database size %d, expecting %d"),
+ send_size, database_size);
+ exit(1);
+ }
+ free(inbuf.data);
+ free(outbuf.data);
+}
+
+static void
+send_error(krb5_context context, krb5_creds *my_creds, int fd, char *err_text,
+ krb5_error_code err_code)
+{
+ krb5_error error;
+ const char *text;
+ krb5_data outbuf;
+
+ memset(&error, 0, sizeof(error));
+ krb5_us_timeofday(context, &error.ctime, &error.cusec);
+ error.server = my_creds->server;
+ error.client = my_principal;
+ error.error = err_code - ERROR_TABLE_BASE_krb5;
+ if (error.error > 127)
+ error.error = KRB_ERR_GENERIC;
+ text = (err_text != NULL) ? err_text : error_message(err_code);
+ error.text.length = strlen(text) + 1;
+ error.text.data = strdup(text);
+ if (error.text.data) {
+ if (!krb5_mk_error(context, &error, &outbuf)) {
+ (void)krb5_write_message(context, &fd, &outbuf);
+ krb5_free_data_contents(context, &outbuf);
+ }
+ free(error.text.data);
+ }
+}
+
+static void
+update_last_prop_file(char *hostname, char *file_name)
+{
+ char *file_last_prop;
+ int fd;
+ static char last_prop[] = ".last_prop";
+
+ if (asprintf(&file_last_prop, "%s.%s%s", file_name, hostname,
+ last_prop) < 0) {
+ com_err(progname, ENOMEM,
+ _("while allocating filename for update_last_prop_file"));
+ return;
+ }
+ fd = THREEPARAMOPEN(file_last_prop, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (fd < 0) {
+ com_err(progname, errno, _("while creating 'last_prop' file, '%s'"),
+ file_last_prop);
+ free(file_last_prop);
+ return;
+ }
+ write(fd, "", 1);
+ free(file_last_prop);
+ close(fd);
+}