2 * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 static krb5_log_facility *log_facility;
41 static const char *slave_stats_file;
42 static const char *slave_stats_temp_file;
43 static const char *slave_time_missing = "2 min";
44 static const char *slave_time_gone = "5 min";
46 static int time_before_missing;
47 static int time_before_gone;
49 const char *master_hostname;
50 const char *pidfile_basename;
53 make_signal_socket (krb5_context context)
55 #ifndef NO_UNIX_SOCKETS
56 struct sockaddr_un addr;
60 fn = kadm5_log_signal_socket(context);
62 fd = socket (AF_UNIX, SOCK_DGRAM, 0);
64 krb5_err (context, 1, errno, "socket AF_UNIX");
65 memset (&addr, 0, sizeof(addr));
66 addr.sun_family = AF_UNIX;
67 strlcpy (addr.sun_path, fn, sizeof(addr.sun_path));
68 unlink (addr.sun_path);
69 if (bind (fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
70 krb5_err (context, 1, errno, "bind %s", addr.sun_path);
73 struct addrinfo *ai = NULL;
76 kadm5_log_signal_socket_info(context, 1, &ai);
78 fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
79 if (rk_IS_BAD_SOCKET(fd))
80 krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF=%d", ai->ai_family);
82 if (rk_IS_SOCKET_ERROR( bind (fd, ai->ai_addr, ai->ai_addrlen) ))
83 krb5_err (context, 1, rk_SOCK_ERRNO, "bind");
89 make_listen_socket (krb5_context context, const char *port_str)
93 struct sockaddr_in addr;
95 fd = socket (AF_INET, SOCK_STREAM, 0);
96 if (rk_IS_BAD_SOCKET(fd))
97 krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF_INET");
98 setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
99 memset (&addr, 0, sizeof(addr));
100 addr.sin_family = AF_INET;
103 addr.sin_port = krb5_getportbyname (context,
106 if (addr.sin_port == 0) {
110 port = strtol (port_str, &ptr, 10);
111 if (port == 0 && ptr == port_str)
112 krb5_errx (context, 1, "bad port `%s'", port_str);
113 addr.sin_port = htons(port);
116 addr.sin_port = krb5_getportbyname (context, IPROP_SERVICE,
119 if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
120 krb5_err (context, 1, errno, "bind");
121 if (listen(fd, SOMAXCONN) < 0)
122 krb5_err (context, 1, errno, "listen");
129 struct sockaddr_in addr;
131 krb5_auth_context ac;
133 uint32_t version_tstamp;
134 uint32_t version_ack;
137 #define SLAVE_F_DEAD 0x1
138 #define SLAVE_F_AYT 0x2
139 #define SLAVE_F_READY 0x4
141 * We'll use non-blocking I/O so no slave can hold us back.
143 * We call the state left over from a partial write a "tail".
145 * The krb5_data holding an KRB-PRIV will be the write buffer.
148 /* Every message we send is a KRB-PRIV with a 4-byte length prefixed */
149 uint8_t header_buf[4];
153 /* For send_complete() we need an sp as part of the tail */
158 uint8_t header_buf[4];
164 * Continuation for fair diff sending we send N entries at a time.
167 off_t off_next_version; /* offset in log of next diff */
168 uint32_t initial_version; /* at time of previous diff */
169 uint32_t initial_tstamp; /* at time of previous diff */
170 uint32_t last_version_sent;
171 int more; /* need to send more diffs */
176 typedef struct slave slave;
179 check_acl (krb5_context context, const char *name)
185 char *slavefile = NULL;
187 if (asprintf(&slavefile, "%s/slaves", hdb_db_dir(context)) == -1
188 || slavefile == NULL)
189 errx(1, "out of memory");
191 fn = krb5_config_get_string_default(context,
198 fp = fopen (fn, "r");
202 while (fgets(buf, sizeof(buf), fp) != NULL) {
203 buf[strcspn(buf, "\r\n")] = '\0';
204 if (strcmp (buf, name) == 0) {
216 s->flags &= ~SLAVE_F_AYT;
217 s->seen = time(NULL);
221 slave_missing_p (slave *s)
223 if (time(NULL) > s->seen + time_before_missing)
229 slave_gone_p (slave *s)
231 if (time(NULL) > s->seen + time_before_gone)
237 slave_dead(krb5_context context, slave *s)
239 krb5_warnx(context, "slave %s dead", s->name);
241 if (!rk_IS_BAD_SOCKET(s->fd)) {
242 rk_closesocket (s->fd);
243 s->fd = rk_INVALID_SOCKET;
245 s->flags |= SLAVE_F_DEAD;
250 remove_slave (krb5_context context, slave *s, slave **root)
254 if (!rk_IS_BAD_SOCKET(s->fd))
255 rk_closesocket (s->fd);
259 krb5_auth_con_free (context, s->ac);
261 /* Free any pending input/output state */
262 krb5_data_free(&s->input.packet);
263 krb5_data_free(&s->tail.packet);
264 krb5_storage_free(s->tail.dump);
266 for (p = root; *p; p = &(*p)->next)
275 add_slave (krb5_context context, krb5_keytab keytab, slave **root,
278 krb5_principal server;
282 krb5_ticket *ticket = NULL;
285 s = calloc(1, sizeof(*s));
287 krb5_warnx (context, "add_slave: no memory");
292 s->input.packet.data = NULL;
293 s->tail.header.data = NULL;
294 s->tail.packet.data = NULL;
297 addr_len = sizeof(s->addr);
298 s->fd = accept (fd, (struct sockaddr *)&s->addr, &addr_len);
299 if (rk_IS_BAD_SOCKET(s->fd)) {
300 krb5_warn (context, rk_SOCK_ERRNO, "accept");
305 strlcpy(hostname, master_hostname, sizeof(hostname));
307 gethostname(hostname, sizeof(hostname));
309 ret = krb5_sname_to_principal (context, hostname, IPROP_NAME,
310 KRB5_NT_SRV_HST, &server);
312 krb5_warn (context, ret, "krb5_sname_to_principal");
316 ret = krb5_recvauth (context, &s->ac, &s->fd,
317 IPROP_VERSION, server, 0, keytab, &ticket);
320 * We'll be doing non-blocking I/O only after authentication. We don't
321 * want to get stuck talking to any one slave.
323 * If we get a partial write, we'll finish writing when the socket becomes
326 * Partial reads will be treated as EOF, causing the slave to be marked
329 * To do non-blocking I/O for authentication we'll have to implement our
330 * own krb5_recvauth().
332 socket_set_nonblocking(s->fd, 1);
335 * We write message lengths separately from the payload, and may do
336 * back-to-back small writes when flushing pending input and then a new
337 * update. Avoid Nagle delays.
339 #if defined(IPPROTO_TCP) && defined(TCP_NODELAY)
342 (void) setsockopt(s->fd, IPPROTO_TCP, TCP_NODELAY,
343 (void *)&nodelay, sizeof(nodelay));
347 krb5_free_principal (context, server);
349 krb5_warn (context, ret, "krb5_recvauth");
352 ret = krb5_unparse_name (context, ticket->client, &s->name);
353 krb5_free_ticket (context, ticket);
355 krb5_warn (context, ret, "krb5_unparse_name");
358 if (check_acl (context, s->name)) {
359 krb5_warnx (context, "%s not in acl", s->name);
367 if (strcmp(l->name, s->name) == 0)
372 if (l->flags & SLAVE_F_DEAD) {
373 remove_slave(context, l, root);
375 krb5_warnx (context, "second connection from %s", s->name);
381 krb5_warnx (context, "connection from %s", s->name);
391 remove_slave(context, s, root);
395 dump_one (krb5_context context, HDB *db, hdb_entry_ex *entry, void *v)
398 krb5_storage *dump = (krb5_storage *)v;
402 ret = hdb_entry2value (context, &entry->entry, &data);
405 ret = krb5_data_realloc (&data, data.length + 4);
408 memmove ((char *)data.data + 4, data.data, data.length - 4);
409 sp = krb5_storage_from_data(&data);
411 ret = krb5_enomem(context);
414 ret = krb5_store_uint32(sp, ONE_PRINC);
415 krb5_storage_free(sp);
418 ret = krb5_store_data(dump, data);
421 krb5_data_free (&data);
426 write_dump (krb5_context context, krb5_storage *dump,
427 const char *database, uint32_t current_version)
435 /* we assume that the caller has obtained an exclusive lock */
437 ret = krb5_storage_truncate(dump, 0);
441 if (krb5_storage_seek(dump, 0, SEEK_SET) != 0)
445 * First we store zero as the HDB version, this will indicate to a
446 * later reader that the dumpfile is invalid. We later write the
447 * correct version in the file after we have written all of the
448 * messages. A dump with a zero version will not be considered
452 ret = krb5_store_uint32(dump, 0);
454 ret = hdb_create (context, &db, database);
456 krb5_err (context, IPROPD_RESTART, ret, "hdb_create: %s", database);
457 ret = db->hdb_open (context, db, O_RDONLY, 0);
459 krb5_err (context, IPROPD_RESTART, ret, "db->open");
461 sp = krb5_storage_from_mem (buf, 4);
463 krb5_errx (context, IPROPD_RESTART, "krb5_storage_from_mem");
464 krb5_store_uint32 (sp, TELL_YOU_EVERYTHING);
465 krb5_storage_free (sp);
470 ret = krb5_store_data(dump, data);
472 krb5_warn (context, ret, "write_dump");
476 ret = hdb_foreach (context, db, HDB_F_ADMIN_DATA, dump_one, dump);
478 krb5_warn (context, ret, "write_dump: hdb_foreach");
482 (*db->hdb_close)(context, db);
483 (*db->hdb_destroy)(context, db);
485 sp = krb5_storage_from_mem (buf, 8);
487 krb5_errx (context, IPROPD_RESTART, "krb5_storage_from_mem");
488 ret = krb5_store_uint32(sp, NOW_YOU_HAVE);
490 krb5_store_uint32(sp, current_version);
491 krb5_storage_free (sp);
496 ret = krb5_store_data(dump, data);
499 * We must ensure that the entire valid dump is written to disk
500 * before we write the current version at the front thus making
501 * it a valid dump file. If we crash around here, this can be
502 * important upon reboot.
506 ret = krb5_storage_fsync(dump);
508 if (ret == 0 && krb5_storage_seek(dump, 0, SEEK_SET) == -1)
511 /* Write current version at the front making the dump valid */
514 ret = krb5_store_uint32(dump, current_version);
517 * We don't need to fsync(2) after the real version is written as
518 * it is not a disaster if it doesn't make it to disk if we crash.
519 * After all, we'll just create a new dumpfile.
523 krb5_warnx(context, "wrote new dumpfile (version %u)",
526 krb5_warn(context, ret, "failed to write new dumpfile (version %u)",
533 mk_priv_tail(krb5_context context, slave *s, krb5_data *data)
538 ret = krb5_mk_priv(context, s->ac, data, &s->tail.packet, NULL);
542 len = s->tail.packet.length;
543 _krb5_put_int(s->tail.header_buf, len, sizeof(s->tail.header_buf));
544 s->tail.header.length = sizeof(s->tail.header_buf);
545 s->tail.header.data = s->tail.header_buf;
552 return s->tail.header.length || s->tail.packet.length || s->tail.dump;
558 return s->next_diff.more;
561 #define SEND_COMPLETE_MAX_RECORDS 50
562 #define SEND_DIFFS_MAX_RECORDS 50
565 send_tail(krb5_context context, slave *s)
577 * For the case where we're continuing a send_complete() send up to
578 * SEND_COMPLETE_MAX_RECORDS records now, and the rest asynchronously
579 * later. This ensures that sending a complete dump to a slow-to-drain
580 * client does not prevent others from getting serviced.
582 for (n = 0; n < SEND_COMPLETE_MAX_RECORDS; n++) {
586 if (s->tail.header.length) {
587 bytes = krb5_net_write(context, &s->fd,
589 s->tail.header.length);
593 s->tail.header.length -= bytes;
594 s->tail.header.data = (char *)s->tail.header.data + bytes;
595 rem = s->tail.header.length;
600 if (s->tail.packet.length) {
601 bytes = krb5_net_write(context, &s->fd,
602 (char *)s->tail.packet.data + s->tail.packet_off,
603 s->tail.packet.length - s->tail.packet_off);
606 s->tail.packet_off += bytes;
609 rem = s->tail.packet.length - s->tail.packet_off;
613 krb5_data_free(&s->tail.packet);
614 s->tail.packet_off = 0;
617 if (s->tail.dump == NULL)
621 * We're in the middle of a send_complete() that was interrupted by
622 * EWOULDBLOCK. Continue the sending of the dump.
624 ret = krb5_ret_data(s->tail.dump, &data);
625 if (ret == HEIM_ERR_EOF) {
626 krb5_storage_free(s->tail.dump);
628 s->version = s->tail.vno;
633 krb5_warn(context, ret, "failed to read entry from dump!");
635 ret = mk_priv_tail(context, s, &data);
636 krb5_data_free(&data);
639 krb5_warn(context, ret, "failed to make and send a KRB-PRIV to %s",
643 slave_dead(context, s);
647 if (ret == 0 && s->tail.dump != NULL)
651 if (errno != EAGAIN && errno != EWOULDBLOCK) {
652 krb5_warn(context, ret = errno,
653 "error sending diffs to now-dead slave %s", s->name);
654 slave_dead(context, s);
660 krb5_warnx(context, "would block writing %llu bytes to slave %s",
661 (unsigned long long)rem, s->name);
666 send_complete(krb5_context context, slave *s, const char *database,
667 uint32_t current_version, uint32_t oldest_version,
668 uint32_t initial_log_tstamp)
671 krb5_storage *dump = NULL;
677 ret = asprintf(&dfn, "%s/ipropd.dumpfile", hdb_db_dir(context));
678 if (ret == -1 || !dfn)
679 return krb5_enomem(context);
681 fd = open(dfn, O_CREAT|O_RDWR, 0600);
684 krb5_warn(context, ret, "Cannot open/create iprop dumpfile %s", dfn);
690 dump = krb5_storage_from_fd(fd);
693 krb5_warn(context, ret, "krb5_storage_from_fd");
698 ret = flock(fd, LOCK_SH);
701 krb5_warn(context, ret, "flock(fd, LOCK_SH)");
705 if (krb5_storage_seek(dump, 0, SEEK_SET) == (off_t)-1) {
707 krb5_warn(context, ret, "krb5_storage_seek(dump, 0, SEEK_SET)");
712 ret = krb5_ret_uint32(dump, &vno);
713 if (ret && ret != HEIM_ERR_EOF) {
714 krb5_warn(context, ret, "krb5_ret_uint32(dump, &vno)");
718 if (fstat(fd, &st) == -1) {
720 krb5_warn(context, ret, "send_complete: could not stat dump file");
725 * If the current dump has an appropriate version, then we can
726 * break out of the loop and send the file below.
728 if (ret == 0 && vno != 0 && st.st_mtime > initial_log_tstamp &&
729 vno >= oldest_version && vno <= current_version)
733 krb5_warnx(context, "send_complete: dumping HDB");
736 * Otherwise, we may need to write a new dump file. We
737 * obtain an exclusive lock on the fd. Because this is
738 * not guaranteed to be an upgrade of our existing shared
739 * lock, someone else may have written a new dumpfile while
740 * we were waiting and so we must first check the vno of
741 * the dump to see if that happened. If it did, we need
742 * to go back to the top of the loop so that we can downgrade
743 * our lock to a shared one.
746 ret = flock(fd, LOCK_EX);
749 krb5_warn(context, ret, "flock(fd, LOCK_EX)");
753 ret = krb5_storage_seek(dump, 0, SEEK_SET);
756 krb5_warn(context, ret, "krb5_storage_seek(dump, 0, SEEK_SET)");
761 ret = krb5_ret_uint32(dump, &vno);
762 if (ret && ret != HEIM_ERR_EOF) {
763 krb5_warn(context, ret, "krb5_ret_uint32(dump, &vno)");
767 if (fstat(fd, &st) == -1) {
769 krb5_warn(context, ret, "send_complete: could not stat dump file");
773 /* check if someone wrote a better version for us */
774 if (ret == 0 && vno != 0 && st.st_mtime > initial_log_tstamp &&
775 vno >= oldest_version && vno <= current_version)
778 /* Now, we know that we must write a new dump file. */
780 ret = write_dump(context, dump, database, current_version);
785 * And we must continue to the top of the loop so that we can
786 * downgrade to a shared lock.
791 * Leaving the above loop, dump should have a ptr right after the initial
792 * 4 byte DB version number and we should have a shared lock on the file
793 * (which we may have just created), so we are reading to start sending
794 * the data down the wire.
796 * Note: (krb5_storage_from_fd() dup()'s the fd)
802 ret = send_tail(context, s);
808 krb5_storage_free(dump);
813 send_are_you_there (krb5_context context, slave *s)
820 if (s->flags & (SLAVE_F_DEAD|SLAVE_F_AYT))
824 * Write any remainder of previous write, if we can. If we'd block we'll
825 * return EWOULDBLOCK.
827 ret = send_tail(context, s);
831 krb5_warnx(context, "slave %s missing, sending AYT", s->name);
833 s->flags |= SLAVE_F_AYT;
838 sp = krb5_storage_from_mem (buf, 4);
840 krb5_warnx (context, "are_you_there: krb5_data_alloc");
841 slave_dead(context, s);
844 ret = krb5_store_uint32(sp, ARE_YOU_THERE);
845 krb5_storage_free (sp);
848 ret = mk_priv_tail(context, s, &data);
850 ret = send_tail(context, s);
851 if (ret && ret != EWOULDBLOCK) {
852 krb5_warn(context, ret, "are_you_there");
853 slave_dead(context, s);
859 diffready(krb5_context context, slave *s)
862 * Don't send any diffs until slave has sent an I_HAVE telling us the
863 * initial version number!
865 if ((s->flags & SLAVE_F_READY) == 0)
868 if (s->flags & SLAVE_F_DEAD) {
870 krb5_warnx(context, "not sending diffs to dead slave %s", s->name);
874 /* Write any remainder of previous write, if we can. */
875 if (send_tail(context, s) != 0)
882 nodiffs(krb5_context context, slave *s, uint32_t current_version)
888 if (s->version < current_version)
892 * If we had sent a partial diff, and now they're caught up, then there's
895 s->next_diff.more = 0;
898 krb5_warnx(context, "slave %s version %ld already sent", s->name,
900 sp = krb5_storage_emem();
902 krb5_errx(context, IPROPD_RESTART, "krb5_storage_from_mem");
904 ret = krb5_store_uint32(sp, YOU_HAVE_LAST_VERSION);
906 krb5_data_zero(&data);
907 ret = krb5_storage_to_data(sp, &data);
909 krb5_storage_free(sp);
911 ret = mk_priv_tail(context, s, &data);
912 krb5_data_free(&data);
915 send_tail(context, s);
921 * Lock the log and return initial version and timestamp
924 get_first(kadm5_server_context *server_context, int log_fd,
925 uint32_t *initial_verp, uint32_t *initial_timep)
927 krb5_context context = server_context->context;
931 * We don't want to perform tight retry loops on log access errors, so on
932 * error mark the slave dead. The slave reconnect after a delay...
934 if (flock(log_fd, LOCK_SH) == -1) {
935 krb5_warn(context, errno, "could not obtain shared lock on log file");
939 ret = kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_FIRST,
940 initial_verp, initial_timep);
941 if (ret == HEIM_ERR_EOF)
942 ret = kadm5_log_get_version_fd(server_context, log_fd,
943 LOG_VERSION_UBER, initial_verp,
946 flock(log_fd, LOCK_UN);
947 krb5_warn(context, ret, "could not read initial log entry");
955 * Find the left end of the diffs in the log we want to send.
957 * - On success, return a positive offset to the first new entry, retaining
958 * a read lock on the log file.
959 * - On error, return a negative offset, with the lock released.
960 * - If we simply find no successor entry in the log, return zero
961 * with the lock released, which indicates that fallback to send_complete()
965 get_left(kadm5_server_context *server_context, slave *s, krb5_storage *sp,
966 int log_fd, uint32_t current_version,
967 uint32_t *initial_verp, uint32_t *initial_timep)
969 krb5_context context = server_context->context;
975 uint32_t ver = s->version;
977 /* This acquires a read lock on success */
978 ret = get_first(server_context, log_fd,
979 initial_verp, initial_timep);
983 /* When the slave version is out of range, send the whole database. */
984 if (ver == 0 || ver < *initial_verp || ver > current_version) {
985 flock(log_fd, LOCK_UN);
989 /* Avoid seeking past the last committed record */
990 if (kadm5_log_goto_end(server_context, sp) != 0 ||
991 (pos = krb5_storage_seek(sp, 0, SEEK_CUR)) < 0)
995 * First try to see if we can find it quickly by seeking to the right
996 * end of the previous diff sent.
998 if (s->next_diff.last_version_sent > 0 &&
999 s->next_diff.off_next_version > 0 &&
1000 s->next_diff.off_next_version < pos &&
1001 s->next_diff.initial_version == *initial_verp &&
1002 s->next_diff.initial_tstamp == *initial_timep) {
1004 * Sanity check that the left version matches what we wanted, the
1005 * log may have been truncated since.
1007 left = s->next_diff.off_next_version;
1008 if (krb5_storage_seek(sp, left, SEEK_SET) != left)
1010 if (kadm5_log_next(context, sp, &ver, NULL, NULL, NULL) == 0 &&
1011 ver == s->next_diff.last_version_sent + 1)
1015 if (krb5_storage_seek(sp, pos, SEEK_SET) != pos)
1019 * Drop the lock and try to find the left entry by seeking backward
1020 * from the end of the end of the log. If we succeed, re-acquire the
1021 * lock, update "next_diff", and retry the fast-path.
1023 flock(log_fd, LOCK_UN);
1025 /* Slow path: seek backwards, entry by entry, from the end */
1030 ret = kadm5_log_previous(context, sp, &ver, NULL, &op, &len);
1033 left = krb5_storage_seek(sp, -16, SEEK_CUR);
1036 if (ver == s->version + 1)
1040 * We don't expect to reach the slave's version, unless the log
1041 * has been modified after we released the lock.
1043 if (ver == s->version) {
1044 krb5_warnx(context, "iprop log truncated while sending diffs "
1045 "to slave?? ver = %lu", (unsigned long)ver);
1049 /* If we've reached the uber record, send the complete database */
1050 if (left == 0 || (ver == 0 && op == kadm_nop))
1053 assert(ver == s->version + 1);
1055 /* Set up the fast-path pre-conditions */
1056 s->next_diff.last_version_sent = s->version;
1057 s->next_diff.off_next_version = left;
1058 s->next_diff.initial_version = *initial_verp;
1059 s->next_diff.initial_tstamp = *initial_timep;
1062 * If we loop then we're hoping to hit the fast path so we can return a
1063 * non-zero, positive left offset with the lock held.
1065 * We just updated the fast path pre-conditions, so unless a log
1066 * truncation event happens between the point where we dropped the lock
1067 * and the point where we rearcuire it above, we will hit the fast
1073 flock(log_fd, LOCK_UN);
1078 get_right(krb5_context context, int log_fd, krb5_storage *sp,
1079 int lastver, slave *s, off_t left, uint32_t *verp)
1083 uint32_t ver = s->version;
1084 off_t right = krb5_storage_seek(sp, left, SEEK_SET);
1087 flock(log_fd, LOCK_UN);
1091 /* The "lastver" bound should preclude us reaching EOF */
1092 for (; ret == 0 && i < SEND_DIFFS_MAX_RECORDS && ver < lastver; ++i) {
1095 ret = kadm5_log_next(context, sp, &logver, NULL, NULL, NULL);
1096 if (logver != ++ver)
1097 ret = KADM5_LOG_CORRUPT;
1101 right = krb5_storage_seek(sp, 0, SEEK_CUR);
1105 flock(log_fd, LOCK_UN);
1113 send_diffs(kadm5_server_context *server_context, slave *s, int log_fd,
1114 const char *database, uint32_t current_version)
1116 krb5_context context = server_context->context;
1118 uint32_t initial_version;
1119 uint32_t initial_tstamp;
1127 if (!diffready(context, s) || nodiffs(context, s, current_version))
1131 krb5_warnx(context, "sending diffs to live-seeming slave %s", s->name);
1133 sp = krb5_storage_from_fd(log_fd);
1135 krb5_err(context, IPROPD_RESTART_SLOW, ENOMEM,
1136 "send_diffs: out of memory");
1138 left = get_left(server_context, s, sp, log_fd, current_version,
1139 &initial_version, &initial_tstamp);
1141 krb5_storage_free(sp);
1142 slave_dead(context, s);
1147 /* Slave's version is not in the log, fall back on send_complete() */
1148 krb5_storage_free(sp);
1149 send_complete(context, s, database, current_version,
1150 initial_version, initial_tstamp);
1154 /* We still hold the read lock, if right > 0 */
1155 right = get_right(server_context->context, log_fd, sp, current_version,
1157 if (right == left) {
1158 flock(log_fd, LOCK_UN);
1159 krb5_storage_free(sp);
1164 krb5_storage_free(sp);
1165 slave_dead(context, s);
1169 if (krb5_storage_seek(sp, left, SEEK_SET) != left) {
1170 ret = errno ? errno : EIO;
1171 flock(log_fd, LOCK_UN);
1172 krb5_warn(context, ret, "send_diffs: krb5_storage_seek");
1173 krb5_storage_free(sp);
1174 slave_dead(context, s);
1178 ret = krb5_data_alloc(&data, right - left + 4);
1180 flock(log_fd, LOCK_UN);
1181 krb5_warn(context, ret, "send_diffs: krb5_data_alloc");
1182 krb5_storage_free(sp);
1183 slave_dead(context, s);
1187 bytes = krb5_storage_read(sp, (char *)data.data + 4, data.length - 4);
1188 flock(log_fd, LOCK_UN);
1189 krb5_storage_free(sp);
1190 if (bytes != data.length - 4)
1191 krb5_errx(context, IPROPD_RESTART, "locked log truncated???");
1193 sp = krb5_storage_from_data(&data);
1195 krb5_err(context, IPROPD_RESTART_SLOW, ENOMEM, "out of memory");
1198 krb5_store_uint32(sp, FOR_YOU);
1199 krb5_storage_free(sp);
1201 ret = mk_priv_tail(context, s, &data);
1202 krb5_data_free(&data);
1204 /* Save the fast-path continuation */
1205 s->next_diff.last_version_sent = ver;
1206 s->next_diff.off_next_version = right;
1207 s->next_diff.initial_version = initial_version;
1208 s->next_diff.initial_tstamp = initial_tstamp;
1209 s->next_diff.more = ver < current_version;
1210 ret = send_tail(context, s);
1213 "syncing slave %s from version %lu to version %lu",
1214 s->name, (unsigned long)s->version,
1215 (unsigned long)ver);
1219 if (ret && ret != EWOULDBLOCK) {
1220 krb5_warn(context, ret, "send_diffs: making or sending "
1221 "KRB-PRIV message");
1222 slave_dead(context, s);
1229 /* Sensible bound on slave message size */
1230 #define SLAVE_MSG_MAX 65536
1233 fill_input(krb5_context context, slave *s)
1235 krb5_error_code ret;
1237 if (s->input.hlen < 4) {
1238 uint8_t *buf = s->input.header_buf + s->input.hlen;
1239 size_t len = 4 - s->input.hlen;
1240 krb5_ssize_t bytes = krb5_net_read(context, &s->fd, buf, len);
1243 return HEIM_ERR_EOF;
1245 if (errno == EWOULDBLOCK || errno == EAGAIN)
1247 return errno ? errno : EIO;
1249 s->input.hlen += bytes;
1253 buf = s->input.header_buf;
1254 len = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
1255 if (len > SLAVE_MSG_MAX)
1257 ret = krb5_data_alloc(&s->input.packet, len);
1262 if (s->input.offset < s->input.packet.length) {
1263 u_char *buf = (u_char *)s->input.packet.data + s->input.offset;
1264 size_t len = s->input.packet.length - s->input.offset;
1265 krb5_ssize_t bytes = krb5_net_read(context, &s->fd, buf, len);
1268 return HEIM_ERR_EOF;
1270 if (errno == EWOULDBLOCK || errno == EAGAIN)
1272 return errno ? errno : EIO;
1274 s->input.offset += bytes;
1282 read_msg(krb5_context context, slave *s, krb5_data *out)
1284 int ret = fill_input(context, s);
1289 ret = krb5_rd_priv(context, s->ac, &s->input.packet, out, NULL);
1291 /* Prepare for next packet */
1292 krb5_data_free(&s->input.packet);
1293 s->input.offset = 0;
1300 process_msg(kadm5_server_context *server_context, slave *s, int log_fd,
1301 const char *database, uint32_t current_version)
1303 krb5_context context = server_context->context;
1309 ret = read_msg(context, s, &out);
1311 if (ret != EWOULDBLOCK)
1312 krb5_warn(context, ret, "error reading message from %s", s->name);
1316 sp = krb5_storage_from_mem(out.data, out.length);
1318 krb5_warnx(context, "process_msg: no memory");
1319 krb5_data_free(&out);
1322 if (krb5_ret_uint32(sp, &tmp) != 0) {
1323 krb5_warnx(context, "process_msg: client send too short command");
1324 krb5_data_free(&out);
1329 ret = krb5_ret_uint32(sp, &tmp);
1331 krb5_warnx(context, "process_msg: client send too little I_HAVE data");
1335 * XXX Make the slave send the timestamp as well, and try to get it
1336 * here, and pass it to send_diffs().
1339 * New slave whose version number we've not yet seen. If the version
1340 * number is zero, the slave has no data, and we'll send a complete
1341 * database (that happens in send_diffs()). Otherwise, we'll record a
1342 * non-zero initial version and attempt an incremental update.
1344 * NOTE!: Once the slave is "ready" (its first I_HAVE has conveyed its
1345 * initial version), we MUST NOT update s->version to the slave's
1346 * I_HAVE version, since we may already have sent later updates, and
1347 * MUST NOT send them again, otherwise we can get further and further
1348 * out of sync resending larger and larger diffs. The "not yet ready"
1349 * is an essential precondition for setting s->version to the value
1350 * in the I_HAVE message. This happens only once when the slave
1353 if (!(s->flags & SLAVE_F_READY)) {
1354 if (current_version < tmp) {
1355 krb5_warnx(context, "Slave %s (version %u) has later version "
1356 "than the master (version %u) OUT OF SYNC",
1357 s->name, tmp, current_version);
1358 /* Force send_complete() */
1362 * Mark the slave as ready for updates based on incoming signals.
1363 * Prior to the initial I_HAVE, we don't know the slave's version
1364 * number, and MUST not send it anything, since we'll needlessly
1365 * attempt to send the whole database!
1368 s->flags |= SLAVE_F_READY;
1370 krb5_warnx(context, "slave %s ready for updates from version %u",
1373 if ((s->version_ack = tmp) < s->version)
1375 send_diffs(server_context, s, log_fd, database, current_version);
1379 krb5_warnx(context, "slave %s is there", s->name);
1384 krb5_warnx(context, "Ignoring command %d", tmp);
1388 krb5_data_free(&out);
1389 krb5_storage_free(sp);
1396 #define SLAVE_NAME "Name"
1397 #define SLAVE_ADDRESS "Address"
1398 #define SLAVE_VERSION "Version"
1399 #define SLAVE_STATUS "Status"
1400 #define SLAVE_SEEN "Last Seen"
1403 init_stats_names(krb5_context context)
1405 const char *fn = NULL;
1408 if (slave_stats_file)
1409 fn = slave_stats_file;
1410 else if ((fn = krb5_config_get_string(context, NULL, "kdc",
1411 "iprop-stats", NULL)) == NULL) {
1412 if (asprintf(&buf, "%s/slaves-stats", hdb_db_dir(context)) != -1
1418 slave_stats_file = fn;
1419 if (asprintf(&buf, "%s.tmp", fn) != -1 && buf != NULL)
1420 slave_stats_temp_file = buf;
1425 write_master_down(krb5_context context)
1428 time_t t = time(NULL);
1431 if (slave_stats_temp_file != NULL)
1432 fp = fopen(slave_stats_temp_file, "w");
1435 krb5_format_time(context, t, str, sizeof(str), TRUE);
1436 fprintf(fp, "master down at %s\n", str);
1438 if (fclose(fp) != EOF)
1439 rk_rename(slave_stats_temp_file, slave_stats_file);
1443 write_stats(krb5_context context, slave *slaves, uint32_t current_version)
1447 time_t t = time(NULL);
1450 if (slave_stats_temp_file != NULL)
1451 fp = fopen(slave_stats_temp_file, "w");
1455 krb5_format_time(context, t, str, sizeof(str), TRUE);
1456 fprintf(fp, "Status for slaves, last updated: %s\n\n", str);
1458 fprintf(fp, "Master version: %lu\n\n", (unsigned long)current_version);
1460 tbl = rtbl_create();
1466 rtbl_add_column(tbl, SLAVE_NAME, 0);
1467 rtbl_add_column(tbl, SLAVE_ADDRESS, 0);
1468 rtbl_add_column(tbl, SLAVE_VERSION, RTBL_ALIGN_RIGHT);
1469 rtbl_add_column(tbl, SLAVE_STATUS, 0);
1470 rtbl_add_column(tbl, SLAVE_SEEN, 0);
1472 rtbl_set_prefix(tbl, " ");
1473 rtbl_set_column_prefix(tbl, SLAVE_NAME, "");
1477 krb5_error_code ret;
1478 rtbl_add_column_entry(tbl, SLAVE_NAME, slaves->name);
1479 ret = krb5_sockaddr2address (context,
1480 (struct sockaddr*)&slaves->addr, &addr);
1482 krb5_print_address(&addr, str, sizeof(str), NULL);
1483 krb5_free_address(context, &addr);
1484 rtbl_add_column_entry(tbl, SLAVE_ADDRESS, str);
1486 rtbl_add_column_entry(tbl, SLAVE_ADDRESS, "<unknown>");
1488 snprintf(str, sizeof(str), "%u", (unsigned)slaves->version_ack);
1489 rtbl_add_column_entry(tbl, SLAVE_VERSION, str);
1491 if (slaves->flags & SLAVE_F_DEAD)
1492 rtbl_add_column_entry(tbl, SLAVE_STATUS, "Down");
1494 rtbl_add_column_entry(tbl, SLAVE_STATUS, "Up");
1496 ret = krb5_format_time(context, slaves->seen, str, sizeof(str), TRUE);
1497 rtbl_add_column_entry(tbl, SLAVE_SEEN, str);
1499 slaves = slaves->next;
1502 rtbl_format(tbl, fp);
1505 if (fclose(fp) != EOF)
1506 rk_rename(slave_stats_temp_file, slave_stats_file);
1510 static char sHDB[] = "HDBGET:";
1512 static int version_flag;
1513 static int help_flag;
1514 static char *keytab_str = sHDB;
1515 static char *database;
1516 static char *config_file;
1517 static char *port_str;
1518 static int detach_from_console;
1519 static int daemon_child = -1;
1521 static struct getargs args[] = {
1522 { "config-file", 'c', arg_string, &config_file, NULL, NULL },
1523 { "realm", 'r', arg_string, &realm, NULL, NULL },
1524 { "keytab", 'k', arg_string, &keytab_str,
1525 "keytab to get authentication from", "kspec" },
1526 { "database", 'd', arg_string, &database, "database", "file"},
1527 { "slave-stats-file", 0, arg_string, rk_UNCONST(&slave_stats_file),
1528 "file for slave status information", "file"},
1529 { "time-missing", 0, arg_string, rk_UNCONST(&slave_time_missing),
1530 "time before slave is polled for presence", "time"},
1531 { "time-gone", 0, arg_string, rk_UNCONST(&slave_time_gone),
1532 "time of inactivity after which a slave is considered gone", "time"},
1533 { "port", 0, arg_string, &port_str,
1534 "port ipropd will listen to", "port"},
1535 { "detach", 0, arg_flag, &detach_from_console,
1536 "detach from console", NULL },
1537 { "daemon-child", 0, arg_integer, &daemon_child,
1538 "private argument, do not use", NULL },
1539 { "pidfile-basename", 0, arg_string, &pidfile_basename,
1540 "basename of pidfile; private argument for testing", "NAME" },
1541 { "hostname", 0, arg_string, rk_UNCONST(&master_hostname),
1542 "hostname of master (if not same as hostname)", "hostname" },
1543 { "verbose", 0, arg_flag, &verbose, NULL, NULL },
1544 { "version", 0, arg_flag, &version_flag, NULL, NULL },
1545 { "help", 0, arg_flag, &help_flag, NULL, NULL }
1547 static int num_args = sizeof(args) / sizeof(args[0]);
1550 main(int argc, char **argv)
1552 krb5_error_code ret;
1553 krb5_context context;
1555 kadm5_server_context *server_context;
1556 kadm5_config_params conf;
1557 krb5_socket_t signal_fd, listen_fd;
1559 slave *slaves = NULL;
1560 uint32_t current_version = 0, old_version = 0;
1565 int restarter_fd = -1;
1568 setprogname(argv[0]);
1570 if (getarg(args, num_args, argc, argv, &optidx))
1571 krb5_std_usage(1, args, num_args);
1574 krb5_std_usage(0, args, num_args);
1577 print_version(NULL);
1581 if (detach_from_console && daemon_child == -1)
1582 daemon_child = roken_detach_prep(argc, argv, "--daemon-child");
1583 rk_pidfile(pidfile_basename);
1585 ret = krb5_init_context(&context);
1587 errx(1, "krb5_init_context failed: %d", ret);
1591 if (config_file == NULL) {
1592 aret = asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context));
1593 if (aret == -1 || config_file == NULL)
1594 errx(1, "out of memory");
1597 ret = krb5_prepend_config_files_default(config_file, &files);
1599 krb5_err(context, 1, ret, "getting configuration files");
1601 ret = krb5_set_config_files(context, files);
1602 krb5_free_config_files(files);
1604 krb5_err(context, 1, ret, "reading configuration files");
1606 init_stats_names(context);
1608 time_before_gone = parse_time (slave_time_gone, "s");
1609 if (time_before_gone < 0)
1610 krb5_errx (context, 1, "couldn't parse time: %s", slave_time_gone);
1611 time_before_missing = parse_time (slave_time_missing, "s");
1612 if (time_before_missing < 0)
1613 krb5_errx (context, 1, "couldn't parse time: %s", slave_time_missing);
1615 krb5_openlog(context, "ipropd-master", &log_facility);
1616 krb5_set_warn_dest(context, log_facility);
1618 ret = krb5_kt_register(context, &hdb_get_kt_ops);
1620 krb5_err(context, 1, ret, "krb5_kt_register");
1622 ret = krb5_kt_resolve(context, keytab_str, &keytab);
1624 krb5_err(context, 1, ret, "krb5_kt_resolve: %s", keytab_str);
1626 memset(&conf, 0, sizeof(conf));
1628 conf.mask |= KADM5_CONFIG_REALM;
1631 ret = kadm5_init_with_skey_ctx (context,
1632 KADM5_ADMIN_SERVICE,
1634 KADM5_ADMIN_SERVICE,
1638 krb5_err (context, 1, ret, "kadm5_init_with_password_ctx");
1640 server_context = (kadm5_server_context *)kadm_handle;
1642 log_fd = open (server_context->log_context.log_file, O_RDONLY, 0);
1644 krb5_err (context, 1, errno, "open %s",
1645 server_context->log_context.log_file);
1647 if (fstat(log_fd, &st) == -1)
1648 krb5_err(context, 1, errno, "stat %s",
1649 server_context->log_context.log_file);
1651 if (flock(log_fd, LOCK_SH) == -1)
1652 krb5_err(context, 1, errno, "shared flock %s",
1653 server_context->log_context.log_file);
1654 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST,
1655 ¤t_version, NULL);
1656 flock(log_fd, LOCK_UN);
1658 signal_fd = make_signal_socket (context);
1659 listen_fd = make_listen_socket (context, port_str);
1661 krb5_warnx(context, "ipropd-master started at version: %lu",
1662 (unsigned long)current_version);
1664 roken_detach_finish(NULL, daemon_child);
1665 restarter_fd = restarter(context, NULL);
1667 while (exit_flag == 0){
1669 fd_set readset, writeset;
1671 struct timeval to = {30, 0};
1675 #ifndef NO_LIMIT_FD_SETSIZE
1676 if (signal_fd >= FD_SETSIZE || listen_fd >= FD_SETSIZE ||
1677 restarter_fd >= FD_SETSIZE)
1678 krb5_errx (context, IPROPD_RESTART, "fd too large");
1683 FD_SET(signal_fd, &readset);
1684 max_fd = max(max_fd, signal_fd);
1685 FD_SET(listen_fd, &readset);
1686 max_fd = max(max_fd, listen_fd);
1687 if (restarter_fd > -1) {
1688 FD_SET(restarter_fd, &readset);
1689 max_fd = max(max_fd, restarter_fd);
1692 for (p = slaves; p != NULL; p = p->next) {
1693 if (p->flags & SLAVE_F_DEAD)
1695 FD_SET(p->fd, &readset);
1696 if (have_tail(p) || more_diffs(p))
1697 FD_SET(p->fd, &writeset);
1698 max_fd = max(max_fd, p->fd);
1701 ret = select(max_fd + 1, &readset, &writeset, NULL, &to);
1706 krb5_err (context, IPROPD_RESTART, errno, "select");
1709 if (stat(server_context->log_context.log_file, &st2) == -1) {
1710 krb5_warn(context, errno, "could not stat log file by path");
1714 if (st2.st_dev != st.st_dev || st2.st_ino != st.st_ino) {
1715 (void) close(log_fd);
1717 log_fd = open(server_context->log_context.log_file, O_RDONLY, 0);
1719 krb5_err(context, IPROPD_RESTART_SLOW, errno, "open %s",
1720 server_context->log_context.log_file);
1722 if (fstat(log_fd, &st) == -1)
1723 krb5_err(context, IPROPD_RESTART_SLOW, errno, "stat %s",
1724 server_context->log_context.log_file);
1726 if (flock(log_fd, LOCK_SH) == -1)
1727 krb5_err(context, IPROPD_RESTART, errno, "shared flock %s",
1728 server_context->log_context.log_file);
1729 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST,
1730 ¤t_version, NULL);
1731 flock(log_fd, LOCK_UN);
1735 /* Recover from failed transactions */
1736 if (kadm5_log_init_nb(server_context) == 0)
1737 kadm5_log_end(server_context);
1739 if (flock(log_fd, LOCK_SH) == -1)
1740 krb5_err(context, IPROPD_RESTART, errno,
1741 "could not lock log file");
1742 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST,
1743 ¤t_version, NULL);
1744 flock(log_fd, LOCK_UN);
1746 if (current_version > old_version) {
1749 "Missed a signal, updating slaves %lu to %lu",
1750 (unsigned long)old_version,
1751 (unsigned long)current_version);
1752 for (p = slaves; p != NULL; p = p->next) {
1753 if (p->flags & SLAVE_F_DEAD)
1755 send_diffs(server_context, p, log_fd, database,
1758 old_version = current_version;
1762 if (ret && FD_ISSET(restarter_fd, &readset)) {
1763 exit_flag = SIGTERM;
1767 if (ret && FD_ISSET(signal_fd, &readset)) {
1768 #ifndef NO_UNIX_SOCKETS
1769 struct sockaddr_un peer_addr;
1771 struct sockaddr_storage peer_addr;
1773 socklen_t peer_len = sizeof(peer_addr);
1775 if(recvfrom(signal_fd, (void *)&vers, sizeof(vers), 0,
1776 (struct sockaddr *)&peer_addr, &peer_len) < 0) {
1777 krb5_warn (context, errno, "recvfrom");
1782 old_version = current_version;
1783 if (flock(log_fd, LOCK_SH) == -1)
1784 krb5_err(context, IPROPD_RESTART, errno, "shared flock %s",
1785 server_context->log_context.log_file);
1786 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST,
1787 ¤t_version, NULL);
1788 flock(log_fd, LOCK_UN);
1789 if (current_version != old_version) {
1791 * If current_version < old_version then the log got
1792 * truncated and we'll end up doing full propagations.
1794 * Truncating the log when the current version is
1795 * numerically small can lead to race conditions.
1796 * Ideally we should identify log versions as
1797 * {init_or_trunc_time, vno}, then we could not have any
1798 * such race conditions, but this would either require
1799 * breaking backwards compatibility for the protocol or
1800 * adding new messages to it.
1804 "Got a signal, updating slaves %lu to %lu",
1805 (unsigned long)old_version,
1806 (unsigned long)current_version);
1807 for (p = slaves; p != NULL; p = p->next) {
1808 if (p->flags & SLAVE_F_DEAD)
1810 send_diffs(server_context, p, log_fd, database,
1816 "Got a signal, but no update in log version %lu",
1817 (unsigned long)current_version);
1821 for (p = slaves; p != NULL; p = p->next) {
1822 if (!(p->flags & SLAVE_F_DEAD) &&
1823 FD_ISSET(p->fd, &writeset) &&
1824 ((have_tail(p) && send_tail(context, p) == 0) ||
1825 (!have_tail(p) && more_diffs(p)))) {
1826 send_diffs(server_context, p, log_fd, database,
1831 for(p = slaves; p != NULL; p = p->next) {
1832 if (p->flags & SLAVE_F_DEAD)
1834 if (ret && FD_ISSET(p->fd, &readset)) {
1837 ret = process_msg(server_context, p, log_fd, database,
1839 if (ret && ret != EWOULDBLOCK)
1840 slave_dead(context, p);
1841 } else if (slave_gone_p (p))
1842 slave_dead(context, p);
1843 else if (slave_missing_p (p))
1844 send_are_you_there (context, p);
1847 if (ret && FD_ISSET(listen_fd, &readset)) {
1848 add_slave (context, keytab, &slaves, listen_fd);
1852 write_stats(context, slaves, current_version);
1855 if(exit_flag == SIGINT || exit_flag == SIGTERM)
1856 krb5_warnx(context, "%s terminated", getprogname());
1858 else if(exit_flag == SIGXCPU)
1859 krb5_warnx(context, "%s CPU time limit exceeded", getprogname());
1862 krb5_warnx(context, "%s unexpected exit reason: %ld",
1863 getprogname(), (long)exit_flag);
1865 write_master_down(context);