Home | History | Annotate | Line # | Download | only in kadm5
      1 /*	$NetBSD: ipropd_slave.c,v 1.4 2023/06/19 21:41:44 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1997 - 2008 Kungliga Tekniska Hgskolan
      5  * (Royal Institute of Technology, Stockholm, Sweden).
      6  * All rights reserved.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     11  *
     12  * 1. Redistributions of source code must retain the above copyright
     13  *    notice, this list of conditions and the following disclaimer.
     14  *
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * 3. Neither the name of the Institute nor the names of its contributors
     20  *    may be used to endorse or promote products derived from this software
     21  *    without specific prior written permission.
     22  *
     23  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
     24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
     27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     33  * SUCH DAMAGE.
     34  */
     35 
     36 #include "iprop.h"
     37 
     38 __RCSID("$NetBSD: ipropd_slave.c,v 1.4 2023/06/19 21:41:44 christos Exp $");
     39 
     40 static const char *config_name = "ipropd-slave";
     41 
     42 static int verbose;
     43 
     44 static krb5_log_facility *log_facility;
     45 static char five_min[] = "5 min";
     46 static char *server_time_lost = five_min;
     47 static int time_before_lost;
     48 const char *slave_str = NULL;
     49 
     50 static int
     51 connect_to_master (krb5_context context, const char *master,
     52 		   const char *port_str)
     53 {
     54     char port[NI_MAXSERV];
     55     struct addrinfo *ai, *a;
     56     struct addrinfo hints;
     57     int error;
     58     int one = 1;
     59     int s = -1;
     60 
     61     memset(&hints, 0, sizeof(hints));
     62     hints.ai_socktype = SOCK_STREAM;
     63 
     64     if (port_str == NULL) {
     65 	snprintf(port, sizeof(port), "%u", IPROP_PORT);
     66 	port_str = port;
     67     }
     68 
     69     error = getaddrinfo(master, port_str, &hints, &ai);
     70     if (error) {
     71 	krb5_warnx(context, "Failed to get address of to %s: %s",
     72 		   master, gai_strerror(error));
     73 	return -1;
     74     }
     75 
     76     for (a = ai; a != NULL; a = a->ai_next) {
     77 	char node[NI_MAXHOST];
     78 	error = getnameinfo(a->ai_addr, a->ai_addrlen,
     79 			    node, sizeof(node), NULL, 0, NI_NUMERICHOST);
     80 	if (error)
     81 	    strlcpy(node, "[unknown-addr]", sizeof(node));
     82 
     83 	s = socket(a->ai_family, a->ai_socktype, a->ai_protocol);
     84 	if (s < 0)
     85 	    continue;
     86 	if (connect(s, a->ai_addr, a->ai_addrlen) < 0) {
     87 	    krb5_warn(context, errno, "connection failed to %s[%s]",
     88 		      master, node);
     89 	    close(s);
     90 	    continue;
     91 	}
     92 	krb5_warnx(context, "connection successful "
     93 		   "to master: %s[%s]", master, node);
     94 	break;
     95     }
     96     freeaddrinfo(ai);
     97 
     98     if (a == NULL)
     99 	return -1;
    100 
    101     if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one)) < 0)
    102         krb5_warn(context, errno, "setsockopt(SO_KEEPALIVE) failed");
    103 
    104     /*
    105      * We write message lengths separately from the payload, avoid Nagle
    106      * delays.
    107      */
    108 #if defined(IPPROTO_TCP) && defined(TCP_NODELAY)
    109     (void) setsockopt(s, IPPROTO_TCP, TCP_NODELAY,
    110                       (void *)&one, sizeof(one));
    111 #endif
    112 
    113     return s;
    114 }
    115 
    116 static void
    117 get_creds(krb5_context context, const char *keytab_str,
    118 	  krb5_ccache *cache, const char *serverhost)
    119 {
    120     krb5_keytab keytab;
    121     krb5_principal client;
    122     krb5_error_code ret;
    123     krb5_get_init_creds_opt *init_opts;
    124     krb5_creds creds;
    125     char *server;
    126     char keytab_buf[256];
    127     int aret;
    128 
    129     if (keytab_str == NULL) {
    130 	ret = krb5_kt_default_name (context, keytab_buf, sizeof(keytab_buf));
    131 	if (ret)
    132 	    krb5_err (context, 1, ret, "krb5_kt_default_name");
    133 	keytab_str = keytab_buf;
    134     }
    135 
    136     ret = krb5_kt_resolve(context, keytab_str, &keytab);
    137     if(ret)
    138 	krb5_err(context, 1, ret, "%s", keytab_str);
    139 
    140 
    141     ret = krb5_sname_to_principal (context, slave_str, IPROP_NAME,
    142 				   KRB5_NT_SRV_HST, &client);
    143     if (ret) krb5_err(context, 1, ret, "krb5_sname_to_principal");
    144 
    145     ret = krb5_get_init_creds_opt_alloc(context, &init_opts);
    146     if (ret) krb5_err(context, 1, ret, "krb5_get_init_creds_opt_alloc");
    147 
    148     aret = asprintf (&server, "%s/%s", IPROP_NAME, serverhost);
    149     if (aret == -1 || server == NULL)
    150 	krb5_errx (context, 1, "malloc: no memory");
    151 
    152     ret = krb5_get_init_creds_keytab(context, &creds, client, keytab,
    153 				     0, server, init_opts);
    154     free (server);
    155     krb5_get_init_creds_opt_free(context, init_opts);
    156     if(ret) krb5_err(context, 1, ret, "krb5_get_init_creds");
    157 
    158     ret = krb5_kt_close(context, keytab);
    159     if(ret) krb5_err(context, 1, ret, "krb5_kt_close");
    160 
    161     ret = krb5_cc_new_unique(context, krb5_cc_type_memory, NULL, cache);
    162     if(ret) krb5_err(context, 1, ret, "krb5_cc_new_unique");
    163 
    164     ret = krb5_cc_initialize(context, *cache, creds.client);
    165     if(ret) krb5_err(context, 1, ret, "krb5_cc_initialize");
    166 
    167     ret = krb5_cc_store_cred(context, *cache, &creds);
    168     if(ret) krb5_err(context, 1, ret, "krb5_cc_store_cred");
    169 
    170     krb5_free_cred_contents(context, &creds);
    171     krb5_free_principal(context, client);
    172 }
    173 
    174 static krb5_error_code
    175 ihave(krb5_context context, krb5_auth_context auth_context,
    176       int fd, uint32_t version)
    177 {
    178     int ret;
    179     u_char buf[8];
    180     krb5_storage *sp;
    181     krb5_data data;
    182 
    183     sp = krb5_storage_from_mem(buf, 8);
    184     ret = krb5_store_uint32(sp, I_HAVE);
    185     if (ret == 0)
    186         ret = krb5_store_uint32(sp, version);
    187     krb5_storage_free(sp);
    188     data.length = 8;
    189     data.data   = buf;
    190 
    191     if (ret == 0) {
    192         if (verbose)
    193             krb5_warnx(context, "telling master we are at %u", version);
    194 
    195         ret = krb5_write_priv_message(context, auth_context, &fd, &data);
    196         if (ret)
    197             krb5_warn(context, ret, "krb5_write_message");
    198     }
    199     return ret;
    200 }
    201 
    202 #ifndef EDQUOT
    203 /* There's no EDQUOT on WIN32, for example */
    204 #define EDQUOT ENOSPC
    205 #endif
    206 
    207 static int
    208 append_to_log_file(krb5_context context,
    209                    kadm5_server_context *server_context,
    210                    krb5_storage *sp, off_t start, ssize_t slen)
    211 {
    212     size_t len;
    213     ssize_t sret;
    214     off_t log_off;
    215     int ret, ret2;
    216     void *buf;
    217 
    218     if (verbose)
    219         krb5_warnx(context, "appending diffs to log");
    220 
    221     if (slen == 0)
    222         return 0;
    223     if (slen < 0)
    224         return EINVAL;
    225     len = slen;
    226     if (len != slen)
    227         return EOVERFLOW;
    228 
    229     buf = malloc(len);
    230     if (buf == NULL && len != 0) {
    231         krb5_warn(context, errno, "malloc: no memory");
    232         return ENOMEM;
    233     }
    234 
    235     if (krb5_storage_seek(sp, start, SEEK_SET) != start) {
    236         krb5_errx(context, IPROPD_RESTART,
    237                   "krb5_storage_seek() failed"); /* can't happen */
    238     }
    239     sret = krb5_storage_read(sp, buf, len);
    240     if (sret < 0)
    241         return errno;
    242     if (len != (size_t)sret) {
    243         /* Can't happen */
    244         krb5_errx(context, IPROPD_RESTART,
    245                   "short krb5_storage_read() from memory buffer");
    246     }
    247     log_off = lseek(server_context->log_context.log_fd, 0, SEEK_CUR);
    248     if (log_off == -1)
    249         return errno;
    250 
    251     /*
    252      * Use net_write() so we get an errno if less that len bytes were
    253      * written.
    254      */
    255     sret = net_write(server_context->log_context.log_fd, buf, len);
    256     free(buf);
    257     if (sret != slen)
    258         ret = errno;
    259     else
    260         ret = fsync(server_context->log_context.log_fd);
    261     if (ret == 0)
    262         return 0;
    263 
    264     /*
    265      * Attempt to recover from this.  First, truncate the log file
    266      * and reset the fd offset.  Failure to do this -> unlink the
    267      * log file and re-create it.  Since we're the slave, we ought to be
    268      * able to recover from the log being unlinked...
    269      */
    270     if (ftruncate(server_context->log_context.log_fd, log_off) == -1 ||
    271         lseek(server_context->log_context.log_fd, log_off, SEEK_SET) == -1) {
    272         (void) kadm5_log_end(server_context);
    273         if (unlink(server_context->log_context.log_file) == -1) {
    274             krb5_err(context, IPROPD_FATAL, errno,
    275                      "Failed to recover from failure to write log "
    276                      "entries from master to disk");
    277         }
    278         ret2 = kadm5_log_init(server_context);
    279         if (ret2) {
    280             krb5_err(context, IPROPD_RESTART_SLOW, ret2,
    281                      "Failed to initialize log to recover from "
    282                      "failure to write log entries from master to disk");
    283         }
    284     }
    285     if (ret == ENOSPC || ret == EDQUOT || ret == EFBIG) {
    286         /* Unlink the file in these cases. */
    287         krb5_warn(context, IPROPD_RESTART_SLOW,
    288                   "Failed to write log entries from master to disk");
    289         (void) kadm5_log_end(server_context);
    290         if (unlink(server_context->log_context.log_file) == -1) {
    291             krb5_err(context, IPROPD_FATAL, errno,
    292                      "Failed to recover from failure to write log "
    293                      "entries from master to disk");
    294         }
    295         ret2 = kadm5_log_init(server_context);
    296         if (ret2) {
    297             krb5_err(context, IPROPD_RESTART_SLOW, ret2,
    298                      "Failed to initialize log to recover from "
    299                      "failure to write log entries from master to disk");
    300         }
    301         return ret;
    302     }
    303     /*
    304      * All other errors we treat as fatal here.  This includes, for
    305      * example, EIO and EPIPE (sorry, can't log to pipes nor sockets).
    306      */
    307     krb5_err(context, IPROPD_FATAL, ret,
    308              "Failed to write log entries from master to disk");
    309 }
    310 
    311 static int
    312 receive_loop (krb5_context context,
    313 	      krb5_storage *sp,
    314 	      kadm5_server_context *server_context)
    315 {
    316     int ret;
    317     off_t left, right, off;
    318     uint32_t len, vers;
    319 
    320     if (verbose)
    321         krb5_warnx(context, "receiving diffs");
    322 
    323     /*
    324      * Seek to the first entry in the message from the master that is
    325      * past the current version of the local database.
    326      */
    327     do {
    328 	uint32_t timestamp;
    329         uint32_t op;
    330 
    331         if ((ret = krb5_ret_uint32(sp, &vers)) == HEIM_ERR_EOF) {
    332             krb5_warnx(context, "master sent no new iprop entries");
    333             return 0;
    334         }
    335 
    336         /*
    337          * TODO We could do more to validate the entries from the master
    338          * here.  And we could use/reuse more kadm5_log_*() code here.
    339          *
    340          * Alternatively we should trust that the master sent us exactly
    341          * what we needed and just write this to the log file and let
    342          * kadm5_log_recover() do the rest.
    343          */
    344 	if (ret || krb5_ret_uint32(sp, &timestamp) != 0 ||
    345             krb5_ret_uint32(sp, &op) != 0 ||
    346             krb5_ret_uint32(sp, &len) != 0) {
    347 
    348             /*
    349              * This shouldn't happen.  Reconnecting probably won't help
    350              * if it does happen, but by reconnecting we get a chance to
    351              * connect to a new master if a new one is configured.
    352              */
    353             krb5_warnx(context, "iprop entries from master were truncated");
    354             return EINVAL;
    355         }
    356 	if (vers > server_context->log_context.version) {
    357             break;
    358         }
    359         off = krb5_storage_seek(sp, 0, SEEK_CUR);
    360         if (krb5_storage_seek(sp, len + 8, SEEK_CUR) != off + len + 8) {
    361             krb5_warnx(context, "iprop entries from master were truncated");
    362             return EINVAL;
    363         }
    364         if (verbose) {
    365             krb5_warnx(context, "diff contains old log record version "
    366                        "%u %lld %u length %u",
    367                        vers, (long long)timestamp, op, len);
    368         }
    369     } while(vers <= server_context->log_context.version);
    370 
    371     /*
    372      * Read the remaining entries into memory...
    373      */
    374     /* SEEK_CUR is a header into the first entry we care about */
    375     left  = krb5_storage_seek(sp, -16, SEEK_CUR);
    376     right = krb5_storage_seek(sp, 0, SEEK_END);
    377     if (right - left < 24 + len) {
    378         krb5_warnx(context, "iprop entries from master were truncated");
    379         return EINVAL;
    380     }
    381 
    382     /*
    383      * ...and then write them out to the on-disk log.
    384      */
    385 
    386     ret = append_to_log_file(context, server_context, sp, left, right - left);
    387     if (ret)
    388         return ret;
    389 
    390     /*
    391      * Replay the new entries.
    392      */
    393     if (verbose)
    394         krb5_warnx(context, "replaying entries from master");
    395     ret = kadm5_log_recover(server_context, kadm_recover_replay);
    396     if (ret) {
    397         krb5_warn(context, ret, "replay failed");
    398         return ret;
    399     }
    400 
    401     ret = kadm5_log_get_version(server_context, &vers);
    402     if (ret) {
    403         krb5_warn(context, ret,
    404                   "could not get log version after applying diffs!");
    405         return ret;
    406     }
    407     if (verbose)
    408         krb5_warnx(context, "slave at version %u", vers);
    409 
    410     if (vers != server_context->log_context.version) {
    411         krb5_warnx(context, "slave's log_context version (%u) is "
    412                    "inconsistent with log's version (%u)",
    413                    server_context->log_context.version, vers);
    414     }
    415 
    416     return 0;
    417 }
    418 
    419 static int
    420 receive(krb5_context context,
    421         krb5_storage *sp,
    422         kadm5_server_context *server_context)
    423 {
    424     krb5_error_code ret, ret2;
    425 
    426     ret = server_context->db->hdb_open(context,
    427 				       server_context->db,
    428 				       O_RDWR | O_CREAT, 0600);
    429     if (ret)
    430         krb5_err(context, IPROPD_RESTART_SLOW, ret, "db->open");
    431 
    432     ret2 = receive_loop(context, sp, server_context);
    433     if (ret2)
    434 	krb5_warn(context, ret2, "receive from ipropd-master had errors");
    435 
    436     ret = server_context->db->hdb_close(context, server_context->db);
    437     if (ret)
    438         krb5_err(context, IPROPD_RESTART_SLOW, ret, "db->close");
    439 
    440     return ret2;
    441 }
    442 
    443 static void
    444 send_im_here(krb5_context context, int fd,
    445 	     krb5_auth_context auth_context)
    446 {
    447     krb5_storage *sp;
    448     krb5_data data;
    449     krb5_error_code ret;
    450 
    451     ret = krb5_data_alloc(&data, 4);
    452     if (ret)
    453         krb5_err(context, IPROPD_RESTART, ret, "send_im_here");
    454 
    455     sp = krb5_storage_from_data (&data);
    456     if (sp == NULL)
    457         krb5_errx(context, IPROPD_RESTART, "krb5_storage_from_data");
    458     ret = krb5_store_uint32(sp, I_AM_HERE);
    459     krb5_storage_free(sp);
    460 
    461     if (ret == 0) {
    462         ret = krb5_write_priv_message(context, auth_context, &fd, &data);
    463         krb5_data_free(&data);
    464 
    465         if (ret)
    466             krb5_err(context, IPROPD_RESTART, ret, "krb5_write_priv_message");
    467 
    468         if (verbose)
    469             krb5_warnx(context, "pinged master");
    470     }
    471 
    472     return;
    473 }
    474 
    475 static void
    476 reinit_log(krb5_context context,
    477 	   kadm5_server_context *server_context,
    478 	   uint32_t vno)
    479 {
    480     krb5_error_code ret;
    481 
    482     if (verbose)
    483         krb5_warnx(context, "truncating log on slave");
    484 
    485     ret = kadm5_log_reinit(server_context, vno);
    486     if (ret)
    487         krb5_err(context, IPROPD_RESTART_SLOW, ret, "kadm5_log_reinit");
    488 }
    489 
    490 
    491 static krb5_error_code
    492 receive_everything(krb5_context context, int fd,
    493 		   kadm5_server_context *server_context,
    494 		   krb5_auth_context auth_context)
    495 {
    496     int ret;
    497     krb5_data data;
    498     uint32_t vno = 0;
    499     uint32_t opcode;
    500     krb5_storage *sp;
    501 
    502     char *dbname;
    503     HDB *mydb;
    504 
    505     krb5_warnx(context, "receive complete database");
    506 
    507     ret = asprintf(&dbname, "%s-NEW", server_context->db->hdb_name);
    508     if (ret == -1)
    509         krb5_err(context, IPROPD_RESTART, ENOMEM, "asprintf");
    510     ret = hdb_create(context, &mydb, dbname);
    511     if(ret)
    512         krb5_err(context, IPROPD_RESTART, ret, "hdb_create");
    513     free(dbname);
    514 
    515     ret = hdb_set_master_keyfile(context,
    516 				 mydb, server_context->config.stash_file);
    517     if(ret)
    518         krb5_err(context, IPROPD_RESTART, ret, "hdb_set_master_keyfile");
    519 
    520     /* I really want to use O_EXCL here, but given that I can't easily clean
    521        up on error, I won't */
    522     ret = mydb->hdb_open(context, mydb, O_RDWR | O_CREAT | O_TRUNC, 0600);
    523     if (ret)
    524         krb5_err(context, IPROPD_RESTART, ret, "db->open");
    525 
    526     (void) mydb->hdb_set_sync(context, mydb, 0);
    527 
    528     sp = NULL;
    529     krb5_data_zero(&data);
    530     do {
    531 	ret = krb5_read_priv_message(context, auth_context, &fd, &data);
    532 
    533 	if (ret) {
    534 	    krb5_warn(context, ret, "krb5_read_priv_message");
    535 	    goto cleanup;
    536 	}
    537 
    538 	sp = krb5_storage_from_data(&data);
    539 	if (sp == NULL)
    540 	    krb5_errx(context, IPROPD_RESTART, "krb5_storage_from_data");
    541 	krb5_ret_uint32(sp, &opcode);
    542 	if (opcode == ONE_PRINC) {
    543 	    krb5_data fake_data;
    544 	    hdb_entry_ex entry;
    545 
    546 	    krb5_storage_free(sp);
    547 
    548 	    fake_data.data   = (char *)data.data + 4;
    549 	    fake_data.length = data.length - 4;
    550 
    551 	    memset(&entry, 0, sizeof(entry));
    552 
    553 	    ret = hdb_value2entry(context, &fake_data, &entry.entry);
    554 	    if (ret)
    555 		krb5_err(context, IPROPD_RESTART, ret, "hdb_value2entry");
    556 	    ret = mydb->hdb_store(server_context->context,
    557 				  mydb,
    558 				  0, &entry);
    559 	    if (ret)
    560 		krb5_err(context, IPROPD_RESTART_SLOW, ret, "hdb_store");
    561 
    562 	    hdb_free_entry(context, &entry);
    563 	    krb5_data_free(&data);
    564 	} else if (opcode == NOW_YOU_HAVE)
    565 	    ;
    566 	else
    567 	    krb5_errx(context, 1, "strange opcode %d", opcode);
    568     } while (opcode == ONE_PRINC);
    569 
    570     if (opcode != NOW_YOU_HAVE)
    571         krb5_errx(context, IPROPD_RESTART_SLOW,
    572                   "receive_everything: strange %d", opcode);
    573 
    574     krb5_ret_uint32(sp, &vno);
    575     krb5_storage_free(sp);
    576 
    577     reinit_log(context, server_context, vno);
    578 
    579     ret = mydb->hdb_set_sync(context, mydb, 1);
    580     if (ret)
    581         krb5_err(context, IPROPD_RESTART_SLOW, ret, "failed to sync the received HDB");
    582     ret = mydb->hdb_close(context, mydb);
    583     if (ret)
    584         krb5_err(context, IPROPD_RESTART_SLOW, ret, "db->close");
    585 
    586     ret = mydb->hdb_rename(context, mydb, server_context->db->hdb_name);
    587     if (ret)
    588         krb5_err(context, IPROPD_RESTART_SLOW, ret, "db->rename");
    589 
    590 
    591     return 0;
    592 
    593  cleanup:
    594     krb5_data_free(&data);
    595 
    596     if (ret)
    597         krb5_err(context, IPROPD_RESTART_SLOW, ret, "db->close");
    598 
    599     ret = mydb->hdb_destroy(context, mydb);
    600     if (ret)
    601         krb5_err(context, IPROPD_RESTART, ret, "db->destroy");
    602 
    603     krb5_warnx(context, "receive complete database, version %ld", (long)vno);
    604     return ret;
    605 }
    606 
    607 static void
    608 slave_status(krb5_context context,
    609 	     const char *file,
    610 	     const char *status, ...)
    611      __attribute__ ((__format__ (__printf__, 3, 4)));
    612 
    613 
    614 static void
    615 slave_status(krb5_context context,
    616 	     const char *file,
    617 	     const char *fmt, ...)
    618 {
    619     char *status;
    620     char *fmt2;
    621     va_list args;
    622     int len;
    623 
    624     if (asprintf(&fmt2, "%s\n", fmt) == -1 || fmt2 == NULL) {
    625         (void) unlink(file);
    626         return;
    627     }
    628     va_start(args, fmt);
    629     len = vasprintf(&status, fmt2, args);
    630     free(fmt2);
    631     va_end(args);
    632     if (len < 0 || status == NULL) {
    633 	(void) unlink(file);
    634 	return;
    635     }
    636     krb5_warnx(context, "slave status change: %s", status);
    637 
    638     rk_dumpdata(file, status, len);
    639     free(status);
    640 }
    641 
    642 static void
    643 is_up_to_date(krb5_context context, const char *file,
    644 	      kadm5_server_context *server_context)
    645 {
    646     krb5_error_code ret;
    647     char buf[80];
    648     ret = krb5_format_time(context, time(NULL), buf, sizeof(buf), 1);
    649     if (ret) {
    650 	unlink(file);
    651 	return;
    652     }
    653     slave_status(context, file, "up-to-date with version: %lu at %s",
    654 		 (unsigned long)server_context->log_context.version, buf);
    655 }
    656 
    657 static char *status_file;
    658 static char *config_file;
    659 static char *realm;
    660 static int version_flag;
    661 static int help_flag;
    662 static char *keytab_str;
    663 static char *port_str;
    664 static int detach_from_console;
    665 static int daemon_child = -1;
    666 
    667 static struct getargs args[] = {
    668     { "config-file", 'c', arg_string, &config_file, NULL, NULL },
    669     { "realm", 'r', arg_string, &realm, NULL, NULL },
    670     { "keytab", 'k', arg_string, &keytab_str,
    671       "keytab to get authentication from", "kspec" },
    672     { "time-lost", 0, arg_string, &server_time_lost,
    673       "time before server is considered lost", "time" },
    674     { "status-file", 0, arg_string, &status_file,
    675       "file to write out status into", "file" },
    676     { "port", 0, arg_string, &port_str,
    677       "port ipropd-slave will connect to", "port"},
    678     { "detach", 0, arg_flag, &detach_from_console,
    679       "detach from console", NULL },
    680     { "daemon-child",       0 ,      arg_integer, &daemon_child,
    681       "private argument, do not use", NULL },
    682     { "hostname", 0, arg_string, rk_UNCONST(&slave_str),
    683       "hostname of slave (if not same as hostname)", "hostname" },
    684     { "verbose", 0, arg_flag, &verbose, NULL, NULL },
    685     { "version", 0, arg_flag, &version_flag, NULL, NULL },
    686     { "help", 0, arg_flag, &help_flag, NULL, NULL }
    687 };
    688 
    689 static int num_args = sizeof(args) / sizeof(args[0]);
    690 
    691 static void
    692 usage(int status)
    693 {
    694     arg_printusage(args, num_args, NULL, "master");
    695     exit(status);
    696 }
    697 
    698 int
    699 main(int argc, char **argv)
    700 {
    701     krb5_error_code ret, ret2;
    702     krb5_context context;
    703     krb5_auth_context auth_context;
    704     void *kadm_handle;
    705     kadm5_server_context *server_context;
    706     kadm5_config_params conf;
    707     int master_fd;
    708     krb5_ccache ccache;
    709     krb5_principal server;
    710     char **files;
    711     int optidx = 0;
    712     time_t reconnect_min;
    713     time_t backoff;
    714     time_t reconnect_max;
    715     time_t reconnect;
    716     time_t before = 0;
    717     int restarter_fd = -1;
    718 
    719     const char *master;
    720 
    721     setprogname(argv[0]);
    722 
    723     if (getarg(args, num_args, argc, argv, &optidx))
    724 	usage(1);
    725 
    726     if (help_flag)
    727 	usage(0);
    728 
    729     if (version_flag) {
    730 	print_version(NULL);
    731 	exit(0);
    732     }
    733 
    734     if (detach_from_console && daemon_child == -1)
    735         roken_detach_prep(argc, argv, "--daemon-child");
    736     rk_pidfile(NULL);
    737 
    738     ret = krb5_init_context(&context);
    739     if (ret)
    740 	errx (1, "krb5_init_context failed: %d", ret);
    741 
    742     setup_signal();
    743 
    744     if (config_file == NULL) {
    745 	if (asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context)) == -1
    746 	    || config_file == NULL)
    747 	    errx(1, "out of memory");
    748     }
    749 
    750     ret = krb5_prepend_config_files_default(config_file, &files);
    751     if (ret)
    752 	krb5_err(context, 1, ret, "getting configuration files");
    753 
    754     ret = krb5_set_config_files(context, files);
    755     krb5_free_config_files(files);
    756     if (ret)
    757 	krb5_err(context, 1, ret, "reading configuration files");
    758 
    759     argc -= optidx;
    760     argv += optidx;
    761 
    762     if (argc != 1)
    763 	usage(1);
    764 
    765     master = argv[0];
    766 
    767     if (status_file == NULL) {
    768 	if (asprintf(&status_file,  "%s/ipropd-slave-status", hdb_db_dir(context)) < 0 || status_file == NULL)
    769 	    krb5_errx(context, 1, "can't allocate status file buffer");
    770     }
    771 
    772     krb5_openlog(context, "ipropd-slave", &log_facility);
    773     krb5_set_warn_dest(context, log_facility);
    774 
    775     slave_status(context, status_file, "bootstrapping");
    776 
    777     ret = krb5_kt_register(context, &hdb_get_kt_ops);
    778     if(ret)
    779 	krb5_err(context, 1, ret, "krb5_kt_register");
    780 
    781     time_before_lost = parse_time (server_time_lost,  "s");
    782     if (time_before_lost < 0)
    783 	krb5_errx (context, 1, "couldn't parse time: %s", server_time_lost);
    784 
    785     slave_status(context, status_file, "getting credentials from keytab/database");
    786 
    787     memset(&conf, 0, sizeof(conf));
    788     if(realm) {
    789 	conf.mask |= KADM5_CONFIG_REALM;
    790 	conf.realm = realm;
    791     }
    792     ret = kadm5_init_with_password_ctx (context,
    793 					KADM5_ADMIN_SERVICE,
    794 					NULL,
    795 					KADM5_ADMIN_SERVICE,
    796 					&conf, 0, 0,
    797 					&kadm_handle);
    798     if (ret)
    799 	krb5_err (context, 1, ret, "kadm5_init_with_password_ctx");
    800 
    801     server_context = (kadm5_server_context *)kadm_handle;
    802 
    803     slave_status(context, status_file, "creating log file");
    804 
    805     ret = kadm5_log_init (server_context);
    806     if (ret)
    807 	krb5_err (context, 1, ret, "kadm5_log_init");
    808 
    809     get_creds(context, keytab_str, &ccache, master);
    810 
    811     ret = krb5_sname_to_principal (context, master, IPROP_NAME,
    812 				   KRB5_NT_SRV_HST, &server);
    813     if (ret)
    814 	krb5_err (context, 1, ret, "krb5_sname_to_principal");
    815 
    816     auth_context = NULL;
    817     master_fd = -1;
    818 
    819     krb5_appdefault_time(context, config_name, NULL, "reconnect-min",
    820 			 10, &reconnect_min);
    821     krb5_appdefault_time(context, config_name, NULL, "reconnect-max",
    822 			 300, &reconnect_max);
    823     krb5_appdefault_time(context, config_name, NULL, "reconnect-backoff",
    824 			 10, &backoff);
    825     reconnect = reconnect_min;
    826 
    827     slave_status(context, status_file, "ipropd-slave started");
    828 
    829     roken_detach_finish(NULL, daemon_child);
    830     restarter_fd = restarter(context, NULL);
    831 
    832     while (!exit_flag) {
    833         struct timeval to;
    834 	time_t now, elapsed;
    835         fd_set readset;
    836 	int connected = FALSE;
    837 
    838 #ifndef NO_LIMIT_FD_SETSIZE
    839         if (restarter_fd >= FD_SETSIZE)
    840             krb5_errx(context, IPROPD_RESTART, "fd too large");
    841 #endif
    842 
    843         FD_ZERO(&readset);
    844         if (restarter_fd > -1)
    845             FD_SET(restarter_fd, &readset);
    846 
    847 	now = time(NULL);
    848 	elapsed = now - before;
    849 
    850 	if (elapsed < reconnect) {
    851 	    time_t left = reconnect - elapsed;
    852 	    krb5_warnx(context, "sleeping %d seconds before "
    853 		       "retrying to connect", (int)left);
    854             to.tv_sec = left;
    855             to.tv_usec = 0;
    856             if (select(restarter_fd + 1, &readset, NULL, NULL, &to) == 1) {
    857                 exit_flag = SIGTERM;
    858                 continue;
    859             }
    860 	}
    861 	before = now;
    862 
    863 	slave_status(context, status_file, "connecting to master: %s\n", master);
    864 
    865 	master_fd = connect_to_master (context, master, port_str);
    866 	if (master_fd < 0)
    867 	    goto retry;
    868 
    869 	reconnect = reconnect_min;
    870 
    871 	if (auth_context) {
    872 	    krb5_auth_con_free(context, auth_context);
    873 	    auth_context = NULL;
    874 	    krb5_cc_destroy(context, ccache);
    875 	    get_creds(context, keytab_str, &ccache, master);
    876 	}
    877         if (verbose)
    878             krb5_warnx(context, "authenticating to master");
    879 	ret = krb5_sendauth (context, &auth_context, &master_fd,
    880 			     IPROP_VERSION, NULL, server,
    881 			     AP_OPTS_MUTUAL_REQUIRED, NULL, NULL,
    882 			     ccache, NULL, NULL, NULL);
    883 	if (ret) {
    884 	    krb5_warn (context, ret, "krb5_sendauth");
    885 	    goto retry;
    886 	}
    887 
    888 	krb5_warnx(context, "ipropd-slave started at version: %ld",
    889 		   (long)server_context->log_context.version);
    890 
    891 	ret = ihave(context, auth_context, master_fd,
    892 		    server_context->log_context.version);
    893 	if (ret)
    894 	    goto retry;
    895 
    896 	connected = TRUE;
    897 
    898         if (verbose)
    899             krb5_warnx(context, "connected to master");
    900 
    901 	slave_status(context, status_file, "connected to master, waiting instructions");
    902 
    903 	while (connected && !exit_flag) {
    904 	    krb5_data out;
    905 	    krb5_storage *sp;
    906 	    uint32_t tmp;
    907             int max_fd;
    908 
    909 #ifndef NO_LIMIT_FD_SETSIZE
    910 	    if (master_fd >= FD_SETSIZE)
    911                 krb5_errx(context, IPROPD_RESTART, "fd too large");
    912             if (restarter_fd >= FD_SETSIZE)
    913                 krb5_errx(context, IPROPD_RESTART, "fd too large");
    914             max_fd = max(restarter_fd, master_fd);
    915 #endif
    916 
    917 	    FD_ZERO(&readset);
    918 	    FD_SET(master_fd, &readset);
    919             if (restarter_fd != -1)
    920                 FD_SET(restarter_fd, &readset);
    921 
    922 	    to.tv_sec = time_before_lost;
    923 	    to.tv_usec = 0;
    924 
    925 	    ret = select (max_fd + 1,
    926 			  &readset, NULL, NULL, &to);
    927 	    if (ret < 0) {
    928 		if (errno == EINTR)
    929 		    continue;
    930 		else
    931 		    krb5_err (context, 1, errno, "select");
    932 	    }
    933 	    if (ret == 0) {
    934 		krb5_warnx(context, "server didn't send a message "
    935                            "in %d seconds", time_before_lost);
    936 		connected = FALSE;
    937 		continue;
    938 	    }
    939 
    940             if (restarter_fd > -1 && FD_ISSET(restarter_fd, &readset)) {
    941                 if (verbose)
    942                     krb5_warnx(context, "slave restarter exited");
    943                 exit_flag = SIGTERM;
    944             }
    945 
    946             if (!FD_ISSET(master_fd, &readset))
    947                 continue;
    948 
    949             if (verbose)
    950                 krb5_warnx(context, "message from master");
    951 
    952 	    ret = krb5_read_priv_message(context, auth_context, &master_fd, &out);
    953 	    if (ret) {
    954 		krb5_warn(context, ret, "krb5_read_priv_message");
    955 		connected = FALSE;
    956 		continue;
    957 	    }
    958 
    959 	    sp = krb5_storage_from_mem (out.data, out.length);
    960             if (sp == NULL)
    961                 krb5_err(context, IPROPD_RESTART, errno, "krb5_storage_from_mem");
    962 	    ret = krb5_ret_uint32(sp, &tmp);
    963             if (ret == HEIM_ERR_EOF) {
    964                 krb5_warn(context, ret, "master sent zero-length message");
    965                 connected = FALSE;
    966                 continue;
    967             }
    968             if (ret != 0) {
    969                 krb5_warn(context, ret, "couldn't read master's message");
    970                 connected = FALSE;
    971                 continue;
    972             }
    973 
    974             ret = kadm5_log_init(server_context);
    975             if (ret) {
    976                 krb5_err(context, IPROPD_RESTART, ret, "kadm5_log_init while "
    977                          "handling a message from the master");
    978             }
    979 	    switch (tmp) {
    980 	    case FOR_YOU :
    981                 if (verbose)
    982                     krb5_warnx(context, "master sent us diffs");
    983 		ret2 = receive(context, sp, server_context);
    984                 if (ret2)
    985                     krb5_warn(context, ret2,
    986                               "receive from ipropd-master had errors");
    987 		ret = ihave(context, auth_context, master_fd,
    988 			    server_context->log_context.version);
    989 		if (ret || ret2)
    990 		    connected = FALSE;
    991 
    992                 /*
    993                  * If it returns an error, receive() may nonetheless
    994                  * have committed some entries successfully, so we must
    995                  * update the slave_status even if there were errors.
    996                  */
    997                 is_up_to_date(context, status_file, server_context);
    998 		break;
    999 	    case TELL_YOU_EVERYTHING :
   1000                 if (verbose)
   1001                     krb5_warnx(context, "master sent us a full dump");
   1002 		ret = receive_everything(context, master_fd, server_context,
   1003 					 auth_context);
   1004                 if (ret == 0) {
   1005                     ret = ihave(context, auth_context, master_fd,
   1006                                 server_context->log_context.version);
   1007                 }
   1008                 if (ret)
   1009 		    connected = FALSE;
   1010                 else
   1011                     is_up_to_date(context, status_file, server_context);
   1012 		break;
   1013 	    case ARE_YOU_THERE :
   1014                 if (verbose)
   1015                     krb5_warnx(context, "master sent us a ping");
   1016 		is_up_to_date(context, status_file, server_context);
   1017                 /*
   1018                  * We used to send an I_HAVE here.  But the master may send
   1019                  * ARE_YOU_THERE messages in response to local, possibly-
   1020                  * transient errors, and if that happens and we respond with an
   1021                  * I_HAVE then we'll loop hard if the error was not transient.
   1022                  *
   1023                  * So we don't ihave() here.
   1024                  */
   1025 		send_im_here(context, master_fd, auth_context);
   1026 		break;
   1027 	    case YOU_HAVE_LAST_VERSION:
   1028                 if (verbose)
   1029                     krb5_warnx(context, "master tells us we are up to date");
   1030 		is_up_to_date(context, status_file, server_context);
   1031 		break;
   1032 	    case NOW_YOU_HAVE :
   1033 	    case I_HAVE :
   1034 	    case ONE_PRINC :
   1035 	    case I_AM_HERE :
   1036 	    default :
   1037 		krb5_warnx (context, "Ignoring command %d", tmp);
   1038 		break;
   1039 	    }
   1040 	    krb5_storage_free (sp);
   1041 	    krb5_data_free (&out);
   1042 
   1043 	}
   1044 
   1045 	slave_status(context, status_file, "disconnected from master");
   1046     retry:
   1047 	if (connected == FALSE)
   1048 	    krb5_warnx (context, "disconnected for server");
   1049 
   1050 	if (exit_flag)
   1051 	    krb5_warnx (context, "got an exit signal");
   1052 
   1053 	if (master_fd >= 0)
   1054 	    close(master_fd);
   1055 
   1056 	reconnect += backoff;
   1057 	if (reconnect > reconnect_max) {
   1058 	    slave_status(context, status_file, "disconnected from master for a long time");
   1059 	    reconnect = reconnect_max;
   1060 	}
   1061     }
   1062 
   1063     if (status_file) {
   1064         /* XXX It'd be better to leave it saying we're not here */
   1065 	unlink(status_file);
   1066     }
   1067 
   1068     if (0);
   1069 #ifndef NO_SIGXCPU
   1070     else if(exit_flag == SIGXCPU)
   1071 	krb5_warnx(context, "%s CPU time limit exceeded", getprogname());
   1072 #endif
   1073     else if(exit_flag == SIGINT || exit_flag == SIGTERM)
   1074 	krb5_warnx(context, "%s terminated", getprogname());
   1075     else
   1076 	krb5_warnx(context, "%s unexpected exit reason: %ld",
   1077 		       getprogname(), (long)exit_flag);
   1078 
   1079     return 0;
   1080 }
   1081