Path: blob/main/crypto/heimdal/lib/kadm5/ipropd_slave.c
34869 views
/*1* Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan2* (Royal Institute of Technology, Stockholm, Sweden).3* All rights reserved.4*5* Redistribution and use in source and binary forms, with or without6* modification, are permitted provided that the following conditions7* are met:8*9* 1. Redistributions of source code must retain the above copyright10* notice, this list of conditions and the following disclaimer.11*12* 2. Redistributions in binary form must reproduce the above copyright13* notice, this list of conditions and the following disclaimer in the14* documentation and/or other materials provided with the distribution.15*16* 3. Neither the name of the Institute nor the names of its contributors17* may be used to endorse or promote products derived from this software18* without specific prior written permission.19*20* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND21* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE22* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE23* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE24* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL25* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS26* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)27* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT28* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY29* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF30* SUCH DAMAGE.31*/3233#include "iprop.h"3435RCSID("$Id$");3637static const char *config_name = "ipropd-slave";3839static krb5_log_facility *log_facility;40static char five_min[] = "5 min";41static char *server_time_lost = five_min;42static int time_before_lost;43const char *slave_str = NULL;4445static int46connect_to_master (krb5_context context, const char *master,47const char *port_str)48{49char port[NI_MAXSERV];50struct addrinfo *ai, *a;51struct addrinfo hints;52int error;53int s = -1;5455memset (&hints, 0, sizeof(hints));56hints.ai_socktype = SOCK_STREAM;5758if (port_str == NULL) {59snprintf(port, sizeof(port), "%u", IPROP_PORT);60port_str = port;61}6263error = getaddrinfo (master, port_str, &hints, &ai);64if (error) {65krb5_warnx(context, "Failed to get address of to %s: %s",66master, gai_strerror(error));67return -1;68}6970for (a = ai; a != NULL; a = a->ai_next) {71char node[NI_MAXHOST];72error = getnameinfo(a->ai_addr, a->ai_addrlen,73node, sizeof(node), NULL, 0, NI_NUMERICHOST);74if (error)75strlcpy(node, "[unknown-addr]", sizeof(node));7677s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);78if (s < 0)79continue;80if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {81krb5_warn(context, errno, "connection failed to %s[%s]",82master, node);83close (s);84continue;85}86krb5_warnx(context, "connection successful "87"to master: %s[%s]", master, node);88break;89}90freeaddrinfo (ai);9192if (a == NULL)93return -1;9495return s;96}9798static void99get_creds(krb5_context context, const char *keytab_str,100krb5_ccache *cache, const char *serverhost)101{102krb5_keytab keytab;103krb5_principal client;104krb5_error_code ret;105krb5_get_init_creds_opt *init_opts;106krb5_creds creds;107char *server;108char keytab_buf[256];109110if (keytab_str == NULL) {111ret = krb5_kt_default_name (context, keytab_buf, sizeof(keytab_buf));112if (ret)113krb5_err (context, 1, ret, "krb5_kt_default_name");114keytab_str = keytab_buf;115}116117ret = krb5_kt_resolve(context, keytab_str, &keytab);118if(ret)119krb5_err(context, 1, ret, "%s", keytab_str);120121122ret = krb5_sname_to_principal (context, slave_str, IPROP_NAME,123KRB5_NT_SRV_HST, &client);124if (ret) krb5_err(context, 1, ret, "krb5_sname_to_principal");125126ret = krb5_get_init_creds_opt_alloc(context, &init_opts);127if (ret) krb5_err(context, 1, ret, "krb5_get_init_creds_opt_alloc");128129asprintf (&server, "%s/%s", IPROP_NAME, serverhost);130if (server == NULL)131krb5_errx (context, 1, "malloc: no memory");132133ret = krb5_get_init_creds_keytab(context, &creds, client, keytab,1340, server, init_opts);135free (server);136krb5_get_init_creds_opt_free(context, init_opts);137if(ret) krb5_err(context, 1, ret, "krb5_get_init_creds");138139ret = krb5_kt_close(context, keytab);140if(ret) krb5_err(context, 1, ret, "krb5_kt_close");141142ret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, cache);143if(ret) krb5_err(context, 1, ret, "krb5_cc_new_unique");144145ret = krb5_cc_initialize(context, *cache, client);146if(ret) krb5_err(context, 1, ret, "krb5_cc_initialize");147148ret = krb5_cc_store_cred(context, *cache, &creds);149if(ret) krb5_err(context, 1, ret, "krb5_cc_store_cred");150151krb5_free_cred_contents(context, &creds);152krb5_free_principal(context, client);153}154155static krb5_error_code156ihave (krb5_context context, krb5_auth_context auth_context,157int fd, uint32_t version)158{159int ret;160u_char buf[8];161krb5_storage *sp;162krb5_data data;163164sp = krb5_storage_from_mem (buf, 8);165krb5_store_int32 (sp, I_HAVE);166krb5_store_int32 (sp, version);167krb5_storage_free (sp);168data.length = 8;169data.data = buf;170171ret = krb5_write_priv_message(context, auth_context, &fd, &data);172if (ret)173krb5_warn (context, ret, "krb5_write_message");174return ret;175}176177static void178receive_loop (krb5_context context,179krb5_storage *sp,180kadm5_server_context *server_context)181{182int ret;183off_t left, right;184void *buf;185int32_t vers, vers2;186ssize_t sret;187188/*189* Seek to the current version of the local database.190*/191do {192int32_t len, timestamp, tmp;193enum kadm_ops op;194195if(krb5_ret_int32 (sp, &vers) != 0)196return;197krb5_ret_int32 (sp, ×tamp);198krb5_ret_int32 (sp, &tmp);199op = tmp;200krb5_ret_int32 (sp, &len);201if ((uint32_t)vers <= server_context->log_context.version)202krb5_storage_seek(sp, len + 8, SEEK_CUR);203} while((uint32_t)vers <= server_context->log_context.version);204205/*206* Read up rest of the entires into the memory...207*/208left = krb5_storage_seek (sp, -16, SEEK_CUR);209right = krb5_storage_seek (sp, 0, SEEK_END);210buf = malloc (right - left);211if (buf == NULL && (right - left) != 0)212krb5_errx (context, 1, "malloc: no memory");213214/*215* ...and then write them out to the on-disk log.216*/217krb5_storage_seek (sp, left, SEEK_SET);218krb5_storage_read (sp, buf, right - left);219sret = write (server_context->log_context.log_fd, buf, right-left);220if (sret != right - left)221krb5_err(context, 1, errno, "Failed to write log to disk");222ret = fsync (server_context->log_context.log_fd);223if (ret)224krb5_err(context, 1, errno, "Failed to sync log to disk");225free (buf);226227/*228* Go back to the startpoint and start to commit the entires to229* the database.230*/231krb5_storage_seek (sp, left, SEEK_SET);232233for(;;) {234int32_t len, len2, timestamp, tmp;235off_t cur, cur2;236enum kadm_ops op;237238if(krb5_ret_int32 (sp, &vers) != 0)239break;240ret = krb5_ret_int32 (sp, ×tamp);241if (ret) krb5_errx(context, 1, "entry %ld: too short", (long)vers);242ret = krb5_ret_int32 (sp, &tmp);243if (ret) krb5_errx(context, 1, "entry %ld: too short", (long)vers);244op = tmp;245ret = krb5_ret_int32 (sp, &len);246if (ret) krb5_errx(context, 1, "entry %ld: too short", (long)vers);247if (len < 0)248krb5_errx(context, 1, "log is corrupted, "249"negative length of entry version %ld: %ld",250(long)vers, (long)len);251cur = krb5_storage_seek(sp, 0, SEEK_CUR);252253krb5_warnx (context, "replaying entry %d", (int)vers);254255ret = kadm5_log_replay (server_context,256op, vers, len, sp);257if (ret) {258const char *s = krb5_get_error_message(server_context->context, ret);259krb5_warnx (context,260"kadm5_log_replay: %ld. Lost entry entry, "261"Database out of sync ?: %s (%d)",262(long)vers, s ? s : "unknown error", ret);263krb5_free_error_message(context, s);264}265266{267/*268* Make sure the krb5_log_replay does the right thing wrt269* reading out data from the sp.270*/271cur2 = krb5_storage_seek(sp, 0, SEEK_CUR);272if (cur + len != cur2)273krb5_errx(context, 1,274"kadm5_log_reply version: %ld didn't read the whole entry",275(long)vers);276}277278if (krb5_ret_int32 (sp, &len2) != 0)279krb5_errx(context, 1, "entry %ld: postamble too short", (long)vers);280if(krb5_ret_int32 (sp, &vers2) != 0)281krb5_errx(context, 1, "entry %ld: postamble too short", (long)vers);282283if (len != len2)284krb5_errx(context, 1, "entry %ld: len != len2", (long)vers);285if (vers != vers2)286krb5_errx(context, 1, "entry %ld: vers != vers2", (long)vers);287}288289/*290* Update version291*/292293server_context->log_context.version = vers;294}295296static void297receive (krb5_context context,298krb5_storage *sp,299kadm5_server_context *server_context)300{301int ret;302303ret = server_context->db->hdb_open(context,304server_context->db,305O_RDWR | O_CREAT, 0600);306if (ret)307krb5_err (context, 1, ret, "db->open");308309receive_loop (context, sp, server_context);310311ret = server_context->db->hdb_close (context, server_context->db);312if (ret)313krb5_err (context, 1, ret, "db->close");314}315316static void317send_im_here (krb5_context context, int fd,318krb5_auth_context auth_context)319{320krb5_storage *sp;321krb5_data data;322int ret;323324ret = krb5_data_alloc (&data, 4);325if (ret)326krb5_err (context, 1, ret, "send_im_here");327328sp = krb5_storage_from_data (&data);329if (sp == NULL)330krb5_errx (context, 1, "krb5_storage_from_data");331krb5_store_int32(sp, I_AM_HERE);332krb5_storage_free(sp);333334ret = krb5_write_priv_message(context, auth_context, &fd, &data);335krb5_data_free(&data);336337if (ret)338krb5_err (context, 1, ret, "krb5_write_priv_message");339}340341static krb5_error_code342receive_everything (krb5_context context, int fd,343kadm5_server_context *server_context,344krb5_auth_context auth_context)345{346int ret;347krb5_data data;348int32_t vno = 0;349int32_t opcode;350krb5_storage *sp;351352char *dbname;353HDB *mydb;354355krb5_warnx(context, "receive complete database");356357asprintf(&dbname, "%s-NEW", server_context->db->hdb_name);358ret = hdb_create(context, &mydb, dbname);359if(ret)360krb5_err(context,1, ret, "hdb_create");361free(dbname);362363ret = hdb_set_master_keyfile (context,364mydb, server_context->config.stash_file);365if(ret)366krb5_err(context,1, ret, "hdb_set_master_keyfile");367368/* I really want to use O_EXCL here, but given that I can't easily clean369up on error, I won't */370ret = mydb->hdb_open(context, mydb, O_RDWR | O_CREAT | O_TRUNC, 0600);371if (ret)372krb5_err (context, 1, ret, "db->open");373374sp = NULL;375do {376ret = krb5_read_priv_message(context, auth_context, &fd, &data);377378if (ret) {379krb5_warn (context, ret, "krb5_read_priv_message");380goto cleanup;381}382383sp = krb5_storage_from_data (&data);384if (sp == NULL)385krb5_errx (context, 1, "krb5_storage_from_data");386krb5_ret_int32 (sp, &opcode);387if (opcode == ONE_PRINC) {388krb5_data fake_data;389hdb_entry_ex entry;390391krb5_storage_free(sp);392393fake_data.data = (char *)data.data + 4;394fake_data.length = data.length - 4;395396memset(&entry, 0, sizeof(entry));397398ret = hdb_value2entry (context, &fake_data, &entry.entry);399if (ret)400krb5_err (context, 1, ret, "hdb_value2entry");401ret = mydb->hdb_store(server_context->context,402mydb,4030, &entry);404if (ret)405krb5_err (context, 1, ret, "hdb_store");406407hdb_free_entry (context, &entry);408krb5_data_free (&data);409} else if (opcode == NOW_YOU_HAVE)410;411else412krb5_errx (context, 1, "strange opcode %d", opcode);413} while (opcode == ONE_PRINC);414415if (opcode != NOW_YOU_HAVE)416krb5_errx (context, 1, "receive_everything: strange %d", opcode);417418krb5_ret_int32 (sp, &vno);419krb5_storage_free(sp);420421ret = kadm5_log_reinit (server_context);422if (ret)423krb5_err(context, 1, ret, "kadm5_log_reinit");424425ret = kadm5_log_set_version (server_context, vno - 1);426if (ret)427krb5_err (context, 1, ret, "kadm5_log_set_version");428429ret = kadm5_log_nop (server_context);430if (ret)431krb5_err (context, 1, ret, "kadm5_log_nop");432433ret = mydb->hdb_rename (context, mydb, server_context->db->hdb_name);434if (ret)435krb5_err (context, 1, ret, "db->rename");436437cleanup:438krb5_data_free (&data);439440ret = mydb->hdb_close (context, mydb);441if (ret)442krb5_err (context, 1, ret, "db->close");443444ret = mydb->hdb_destroy (context, mydb);445if (ret)446krb5_err (context, 1, ret, "db->destroy");447448krb5_warnx(context, "receive complete database, version %ld", (long)vno);449return ret;450}451452static char *config_file;453static char *realm;454static int version_flag;455static int help_flag;456static char *keytab_str;457static char *port_str;458#ifdef SUPPORT_DETACH459static int detach_from_console = 0;460#endif461462static struct getargs args[] = {463{ "config-file", 'c', arg_string, &config_file, NULL, NULL },464{ "realm", 'r', arg_string, &realm, NULL, NULL },465{ "keytab", 'k', arg_string, &keytab_str,466"keytab to get authentication from", "kspec" },467{ "time-lost", 0, arg_string, &server_time_lost,468"time before server is considered lost", "time" },469{ "port", 0, arg_string, &port_str,470"port ipropd-slave will connect to", "port"},471#ifdef SUPPORT_DETACH472{ "detach", 0, arg_flag, &detach_from_console,473"detach from console", NULL },474#endif475{ "hostname", 0, arg_string, rk_UNCONST(&slave_str),476"hostname of slave (if not same as hostname)", "hostname" },477{ "version", 0, arg_flag, &version_flag, NULL, NULL },478{ "help", 0, arg_flag, &help_flag, NULL, NULL }479};480481static int num_args = sizeof(args) / sizeof(args[0]);482483static void484usage(int status)485{486arg_printusage(args, num_args, NULL, "master");487exit(status);488}489490int491main(int argc, char **argv)492{493krb5_error_code ret;494krb5_context context;495krb5_auth_context auth_context;496void *kadm_handle;497kadm5_server_context *server_context;498kadm5_config_params conf;499int master_fd;500krb5_ccache ccache;501krb5_principal server;502char **files;503int optidx = 0;504time_t reconnect_min;505time_t backoff;506time_t reconnect_max;507time_t reconnect;508time_t before = 0;509510const char *master;511512setprogname(argv[0]);513514if(getarg(args, num_args, argc, argv, &optidx))515usage(1);516517if(help_flag)518usage(0);519if(version_flag) {520print_version(NULL);521exit(0);522}523524ret = krb5_init_context(&context);525if (ret)526errx (1, "krb5_init_context failed: %d", ret);527528setup_signal();529530if (config_file == NULL) {531if (asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context)) == -1532|| config_file == NULL)533errx(1, "out of memory");534}535536ret = krb5_prepend_config_files_default(config_file, &files);537if (ret)538krb5_err(context, 1, ret, "getting configuration files");539540ret = krb5_set_config_files(context, files);541krb5_free_config_files(files);542if (ret)543krb5_err(context, 1, ret, "reading configuration files");544545argc -= optidx;546argv += optidx;547548if (argc != 1)549usage(1);550551master = argv[0];552553#ifdef SUPPORT_DETACH554if (detach_from_console)555daemon(0, 0);556#endif557pidfile (NULL);558krb5_openlog (context, "ipropd-slave", &log_facility);559krb5_set_warn_dest(context, log_facility);560561ret = krb5_kt_register(context, &hdb_kt_ops);562if(ret)563krb5_err(context, 1, ret, "krb5_kt_register");564565time_before_lost = parse_time (server_time_lost, "s");566if (time_before_lost < 0)567krb5_errx (context, 1, "couldn't parse time: %s", server_time_lost);568569memset(&conf, 0, sizeof(conf));570if(realm) {571conf.mask |= KADM5_CONFIG_REALM;572conf.realm = realm;573}574ret = kadm5_init_with_password_ctx (context,575KADM5_ADMIN_SERVICE,576NULL,577KADM5_ADMIN_SERVICE,578&conf, 0, 0,579&kadm_handle);580if (ret)581krb5_err (context, 1, ret, "kadm5_init_with_password_ctx");582583server_context = (kadm5_server_context *)kadm_handle;584585ret = kadm5_log_init (server_context);586if (ret)587krb5_err (context, 1, ret, "kadm5_log_init");588589get_creds(context, keytab_str, &ccache, master);590591ret = krb5_sname_to_principal (context, master, IPROP_NAME,592KRB5_NT_SRV_HST, &server);593if (ret)594krb5_err (context, 1, ret, "krb5_sname_to_principal");595596auth_context = NULL;597master_fd = -1;598599krb5_appdefault_time(context, config_name, NULL, "reconnect-min",60010, &reconnect_min);601krb5_appdefault_time(context, config_name, NULL, "reconnect-max",602300, &reconnect_max);603krb5_appdefault_time(context, config_name, NULL, "reconnect-backoff",60410, &backoff);605reconnect = reconnect_min;606607while (!exit_flag) {608time_t now, elapsed;609int connected = FALSE;610611now = time(NULL);612elapsed = now - before;613614if (elapsed < reconnect) {615time_t left = reconnect - elapsed;616krb5_warnx(context, "sleeping %d seconds before "617"retrying to connect", (int)left);618sleep(left);619}620before = now;621622master_fd = connect_to_master (context, master, port_str);623if (master_fd < 0)624goto retry;625626reconnect = reconnect_min;627628if (auth_context) {629krb5_auth_con_free(context, auth_context);630auth_context = NULL;631krb5_cc_destroy(context, ccache);632get_creds(context, keytab_str, &ccache, master);633}634ret = krb5_sendauth (context, &auth_context, &master_fd,635IPROP_VERSION, NULL, server,636AP_OPTS_MUTUAL_REQUIRED, NULL, NULL,637ccache, NULL, NULL, NULL);638if (ret) {639krb5_warn (context, ret, "krb5_sendauth");640goto retry;641}642643krb5_warnx(context, "ipropd-slave started at version: %ld",644(long)server_context->log_context.version);645646ret = ihave (context, auth_context, master_fd,647server_context->log_context.version);648if (ret)649goto retry;650651connected = TRUE;652653while (connected && !exit_flag) {654krb5_data out;655krb5_storage *sp;656int32_t tmp;657fd_set readset;658struct timeval to;659660#ifndef NO_LIMIT_FD_SETSIZE661if (master_fd >= FD_SETSIZE)662krb5_errx (context, 1, "fd too large");663#endif664665FD_ZERO(&readset);666FD_SET(master_fd, &readset);667668to.tv_sec = time_before_lost;669to.tv_usec = 0;670671ret = select (master_fd + 1,672&readset, NULL, NULL, &to);673if (ret < 0) {674if (errno == EINTR)675continue;676else677krb5_err (context, 1, errno, "select");678}679if (ret == 0)680krb5_errx (context, 1, "server didn't send a message "681"in %d seconds", time_before_lost);682683ret = krb5_read_priv_message(context, auth_context, &master_fd, &out);684if (ret) {685krb5_warn (context, ret, "krb5_read_priv_message");686connected = FALSE;687continue;688}689690sp = krb5_storage_from_mem (out.data, out.length);691krb5_ret_int32 (sp, &tmp);692switch (tmp) {693case FOR_YOU :694receive (context, sp, server_context);695ret = ihave (context, auth_context, master_fd,696server_context->log_context.version);697if (ret)698connected = FALSE;699break;700case TELL_YOU_EVERYTHING :701ret = receive_everything (context, master_fd, server_context,702auth_context);703if (ret)704connected = FALSE;705break;706case ARE_YOU_THERE :707send_im_here (context, master_fd, auth_context);708break;709case NOW_YOU_HAVE :710case I_HAVE :711case ONE_PRINC :712case I_AM_HERE :713default :714krb5_warnx (context, "Ignoring command %d", tmp);715break;716}717krb5_storage_free (sp);718krb5_data_free (&out);719720}721retry:722if (connected == FALSE)723krb5_warnx (context, "disconnected for server");724if (exit_flag)725krb5_warnx (context, "got an exit signal");726727if (master_fd >= 0)728close(master_fd);729730reconnect += backoff;731if (reconnect > reconnect_max)732reconnect = reconnect_max;733}734735if (0);736#ifndef NO_SIGXCPU737else if(exit_flag == SIGXCPU)738krb5_warnx(context, "%s CPU time limit exceeded", getprogname());739#endif740else if(exit_flag == SIGINT || exit_flag == SIGTERM)741krb5_warnx(context, "%s terminated", getprogname());742else743krb5_warnx(context, "%s unexpected exit reason: %ld",744getprogname(), (long)exit_flag);745746return 0;747}748749750