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
;
51 static char hostname
[128];
54 make_signal_socket (krb5_context context
)
56 #ifndef NO_UNIX_SOCKETS
57 struct sockaddr_un addr
;
61 fn
= kadm5_log_signal_socket(context
);
63 fd
= socket (AF_UNIX
, SOCK_DGRAM
, 0);
65 krb5_err (context
, 1, errno
, "socket AF_UNIX");
66 memset (&addr
, 0, sizeof(addr
));
67 addr
.sun_family
= AF_UNIX
;
68 strlcpy (addr
.sun_path
, fn
, sizeof(addr
.sun_path
));
69 unlink (addr
.sun_path
);
70 if (bind (fd
, (struct sockaddr
*)&addr
, sizeof(addr
)) < 0)
71 krb5_err (context
, 1, errno
, "bind %s", addr
.sun_path
);
74 struct addrinfo
*ai
= NULL
;
77 kadm5_log_signal_socket_info(context
, 1, &ai
);
79 fd
= socket(ai
->ai_family
, ai
->ai_socktype
, ai
->ai_protocol
);
80 if (rk_IS_BAD_SOCKET(fd
))
81 krb5_err (context
, 1, rk_SOCK_ERRNO
, "socket AF=%d", ai
->ai_family
);
83 if (rk_IS_SOCKET_ERROR( bind (fd
, ai
->ai_addr
, ai
->ai_addrlen
) ))
84 krb5_err (context
, 1, rk_SOCK_ERRNO
, "bind");
90 make_listen_socket (krb5_context context
, const char *port_str
)
94 struct sockaddr_in addr
;
96 fd
= socket (AF_INET
, SOCK_STREAM
, 0);
97 if (rk_IS_BAD_SOCKET(fd
))
98 krb5_err (context
, 1, rk_SOCK_ERRNO
, "socket AF_INET");
99 (void) setsockopt(fd
, SOL_SOCKET
, SO_REUSEADDR
, (void *)&one
, sizeof(one
));
100 memset (&addr
, 0, sizeof(addr
));
101 addr
.sin_family
= AF_INET
;
104 addr
.sin_port
= krb5_getportbyname (context
,
107 if (addr
.sin_port
== 0) {
111 port
= strtol (port_str
, &ptr
, 10);
112 if (port
== 0 && ptr
== port_str
)
113 krb5_errx (context
, 1, "bad port `%s'", port_str
);
114 addr
.sin_port
= htons(port
);
117 addr
.sin_port
= krb5_getportbyname (context
, IPROP_SERVICE
,
120 if(bind(fd
, (struct sockaddr
*)&addr
, sizeof(addr
)) < 0)
121 krb5_err (context
, 1, errno
, "bind");
122 if (listen(fd
, SOMAXCONN
) < 0)
123 krb5_err (context
, 1, errno
, "listen");
130 struct sockaddr_in addr
;
132 krb5_auth_context ac
;
134 uint32_t version_tstamp
;
135 uint32_t version_ack
;
138 #define SLAVE_F_DEAD 0x1
139 #define SLAVE_F_AYT 0x2
140 #define SLAVE_F_READY 0x4
142 * We'll use non-blocking I/O so no slave can hold us back.
144 * We call the state left over from a partial write a "tail".
146 * The krb5_data holding an KRB-PRIV will be the write buffer.
149 /* Every message we send is a KRB-PRIV with a 4-byte length prefixed */
150 uint8_t header_buf
[4];
154 /* For send_complete() we need an sp as part of the tail */
159 uint8_t header_buf
[4];
165 * Continuation for fair diff sending we send N entries at a time.
168 off_t off_next_version
; /* offset in log of next diff */
169 uint32_t initial_version
; /* at time of previous diff */
170 uint32_t initial_tstamp
; /* at time of previous diff */
171 uint32_t last_version_sent
;
172 int more
; /* need to send more diffs */
177 typedef struct slave slave
;
180 check_acl (krb5_context context
, const char *name
)
186 char *slavefile
= NULL
;
188 if (asprintf(&slavefile
, "%s/slaves", hdb_db_dir(context
)) == -1
189 || slavefile
== NULL
)
190 errx(1, "out of memory");
192 fn
= krb5_config_get_string_default(context
,
199 fp
= fopen (fn
, "r");
203 while (fgets(buf
, sizeof(buf
), fp
) != NULL
) {
204 buf
[strcspn(buf
, "\r\n")] = '\0';
205 if (strcmp (buf
, name
) == 0) {
217 s
->flags
&= ~SLAVE_F_AYT
;
218 s
->seen
= time(NULL
);
222 slave_missing_p (slave
*s
)
224 if (time(NULL
) > s
->seen
+ time_before_missing
)
230 slave_gone_p (slave
*s
)
232 if (time(NULL
) > s
->seen
+ time_before_gone
)
238 slave_dead(krb5_context context
, slave
*s
)
240 krb5_warnx(context
, "slave %s dead", s
->name
);
242 if (!rk_IS_BAD_SOCKET(s
->fd
)) {
243 rk_closesocket (s
->fd
);
244 s
->fd
= rk_INVALID_SOCKET
;
246 s
->flags
|= SLAVE_F_DEAD
;
251 remove_slave (krb5_context context
, slave
*s
, slave
**root
)
255 if (!rk_IS_BAD_SOCKET(s
->fd
))
256 rk_closesocket (s
->fd
);
260 krb5_auth_con_free (context
, s
->ac
);
262 /* Free any pending input/output state */
263 krb5_data_free(&s
->input
.packet
);
264 krb5_data_free(&s
->tail
.packet
);
265 krb5_storage_free(s
->tail
.dump
);
267 for (p
= root
; *p
; p
= &(*p
)->next
)
276 add_slave (krb5_context context
, krb5_keytab keytab
, slave
**root
,
279 krb5_principal server
= NULL
;
283 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 * We write message lengths separately from the payload, and may do
306 * back-to-back small writes when flushing pending input and then a new
307 * update. Avoid Nagle delays.
309 #if defined(IPPROTO_TCP) && defined(TCP_NODELAY)
312 (void) setsockopt(s
->fd
, IPPROTO_TCP
, TCP_NODELAY
,
313 (void *)&nodelay
, sizeof(nodelay
));
317 ret
= krb5_sname_to_principal (context
, hostname
, IPROP_NAME
,
318 KRB5_NT_SRV_HST
, &server
);
320 krb5_warn (context
, ret
, "krb5_sname_to_principal");
324 ret
= krb5_recvauth (context
, &s
->ac
, &s
->fd
,
325 IPROP_VERSION
, server
, 0, keytab
, &ticket
);
328 * We'll be doing non-blocking I/O only after authentication. We don't
329 * want to get stuck talking to any one slave.
331 * If we get a partial write, we'll finish writing when the socket becomes
334 * Partial reads will be treated as EOF, causing the slave to be marked
337 * To do non-blocking I/O for authentication we'll have to implement our
338 * own krb5_recvauth().
340 socket_set_nonblocking(s
->fd
, 1);
343 krb5_warn (context
, ret
, "krb5_recvauth");
346 ret
= krb5_unparse_name (context
, ticket
->client
, &s
->name
);
348 krb5_warn (context
, ret
, "krb5_unparse_name");
351 if (check_acl (context
, s
->name
)) {
352 krb5_warnx (context
, "%s not in acl", s
->name
);
360 if (strcmp(l
->name
, s
->name
) == 0)
365 if (l
->flags
& SLAVE_F_DEAD
) {
366 remove_slave(context
, l
, root
);
368 krb5_warnx (context
, "second connection from %s", s
->name
);
374 krb5_free_principal(context
, server
);
375 krb5_free_ticket(context
, ticket
);
376 krb5_warnx (context
, "connection from %s", s
->name
);
386 remove_slave(context
, s
, root
);
387 krb5_free_principal(context
, server
);
389 krb5_free_ticket(context
, ticket
);
393 dump_one (krb5_context context
, HDB
*db
, hdb_entry
*entry
, void *v
)
396 krb5_storage
*dump
= (krb5_storage
*)v
;
400 ret
= hdb_entry2value (context
, entry
, &data
);
403 ret
= krb5_data_realloc (&data
, data
.length
+ 4);
406 memmove ((char *)data
.data
+ 4, data
.data
, data
.length
- 4);
407 sp
= krb5_storage_from_data(&data
);
409 ret
= krb5_enomem(context
);
412 ret
= krb5_store_uint32(sp
, ONE_PRINC
);
413 krb5_storage_free(sp
);
416 ret
= krb5_store_data(dump
, data
);
419 krb5_data_free (&data
);
424 write_dump (krb5_context context
, krb5_storage
*dump
,
425 const char *database
, uint32_t current_version
)
433 /* we assume that the caller has obtained an exclusive lock */
435 ret
= krb5_storage_truncate(dump
, 0);
439 if (krb5_storage_seek(dump
, 0, SEEK_SET
) != 0)
443 * First we store zero as the HDB version, this will indicate to a
444 * later reader that the dumpfile is invalid. We later write the
445 * correct version in the file after we have written all of the
446 * messages. A dump with a zero version will not be considered
450 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 ret
= krb5_store_uint32(sp
, FOR_YOU
);
1199 krb5_storage_free(sp
);
1202 ret
= mk_priv_tail(context
, s
, &data
);
1203 krb5_data_free(&data
);
1205 /* Save the fast-path continuation */
1206 s
->next_diff
.last_version_sent
= ver
;
1207 s
->next_diff
.off_next_version
= right
;
1208 s
->next_diff
.initial_version
= initial_version
;
1209 s
->next_diff
.initial_tstamp
= initial_tstamp
;
1210 s
->next_diff
.more
= ver
< current_version
;
1211 ret
= send_tail(context
, s
);
1214 "syncing slave %s from version %lu to version %lu",
1215 s
->name
, (unsigned long)s
->version
,
1216 (unsigned long)ver
);
1220 if (ret
&& ret
!= EWOULDBLOCK
) {
1221 krb5_warn(context
, ret
, "send_diffs: making or sending "
1222 "KRB-PRIV message");
1223 slave_dead(context
, s
);
1230 /* Sensible bound on slave message size */
1231 #define SLAVE_MSG_MAX 65536
1234 fill_input(krb5_context context
, slave
*s
)
1236 krb5_error_code ret
;
1238 if (s
->input
.hlen
< 4) {
1239 uint8_t *buf
= s
->input
.header_buf
+ s
->input
.hlen
;
1240 size_t len
= 4 - s
->input
.hlen
;
1241 krb5_ssize_t bytes
= krb5_net_read(context
, &s
->fd
, buf
, len
);
1244 return HEIM_ERR_EOF
;
1246 if (errno
== EWOULDBLOCK
|| errno
== EAGAIN
)
1248 return errno
? errno
: EIO
;
1250 s
->input
.hlen
+= bytes
;
1254 buf
= s
->input
.header_buf
;
1255 len
= ((unsigned long)buf
[0] << 24) | (buf
[1] << 16)
1256 | (buf
[2] << 8) | buf
[3];
1257 if (len
> SLAVE_MSG_MAX
)
1259 ret
= krb5_data_alloc(&s
->input
.packet
, len
);
1264 if (s
->input
.offset
< s
->input
.packet
.length
) {
1265 u_char
*buf
= (u_char
*)s
->input
.packet
.data
+ s
->input
.offset
;
1266 size_t len
= s
->input
.packet
.length
- s
->input
.offset
;
1267 krb5_ssize_t bytes
= krb5_net_read(context
, &s
->fd
, buf
, len
);
1270 return HEIM_ERR_EOF
;
1272 if (errno
== EWOULDBLOCK
|| errno
== EAGAIN
)
1274 return errno
? errno
: EIO
;
1276 s
->input
.offset
+= bytes
;
1284 read_msg(krb5_context context
, slave
*s
, krb5_data
*out
)
1286 int ret
= fill_input(context
, s
);
1291 ret
= krb5_rd_priv(context
, s
->ac
, &s
->input
.packet
, out
, NULL
);
1293 /* Prepare for next packet */
1294 krb5_data_free(&s
->input
.packet
);
1295 s
->input
.offset
= 0;
1302 process_msg(kadm5_server_context
*server_context
, slave
*s
, int log_fd
,
1303 const char *database
, uint32_t current_version
)
1305 krb5_context context
= server_context
->context
;
1311 ret
= read_msg(context
, s
, &out
);
1313 if (ret
!= EWOULDBLOCK
)
1314 krb5_warn(context
, ret
, "error reading message from %s", s
->name
);
1318 sp
= krb5_storage_from_mem(out
.data
, out
.length
);
1320 krb5_warnx(context
, "process_msg: no memory");
1321 krb5_data_free(&out
);
1324 if (krb5_ret_uint32(sp
, &tmp
) != 0) {
1325 krb5_warnx(context
, "process_msg: client send too short command");
1326 krb5_data_free(&out
);
1331 ret
= krb5_ret_uint32(sp
, &tmp
);
1333 krb5_warnx(context
, "process_msg: client send too little I_HAVE data");
1337 * XXX Make the slave send the timestamp as well, and try to get it
1338 * here, and pass it to send_diffs().
1341 * New slave whose version number we've not yet seen. If the version
1342 * number is zero, the slave has no data, and we'll send a complete
1343 * database (that happens in send_diffs()). Otherwise, we'll record a
1344 * non-zero initial version and attempt an incremental update.
1346 * NOTE!: Once the slave is "ready" (its first I_HAVE has conveyed its
1347 * initial version), we MUST NOT update s->version to the slave's
1348 * I_HAVE version, since we may already have sent later updates, and
1349 * MUST NOT send them again, otherwise we can get further and further
1350 * out of sync resending larger and larger diffs. The "not yet ready"
1351 * is an essential precondition for setting s->version to the value
1352 * in the I_HAVE message. This happens only once when the slave
1355 if (!(s
->flags
& SLAVE_F_READY
)) {
1356 if (current_version
< tmp
) {
1357 krb5_warnx(context
, "Slave %s (version %u) has later version "
1358 "than the master (version %u) OUT OF SYNC",
1359 s
->name
, tmp
, current_version
);
1360 /* Force send_complete() */
1364 * Mark the slave as ready for updates based on incoming signals.
1365 * Prior to the initial I_HAVE, we don't know the slave's version
1366 * number, and MUST not send it anything, since we'll needlessly
1367 * attempt to send the whole database!
1370 s
->flags
|= SLAVE_F_READY
;
1372 krb5_warnx(context
, "slave %s ready for updates from version %u",
1375 if ((s
->version_ack
= tmp
) < s
->version
)
1377 send_diffs(server_context
, s
, log_fd
, database
, current_version
);
1381 krb5_warnx(context
, "slave %s is there", s
->name
);
1386 krb5_warnx(context
, "Ignoring command %d", tmp
);
1390 krb5_data_free(&out
);
1391 krb5_storage_free(sp
);
1398 #define SLAVE_NAME "Name"
1399 #define SLAVE_ADDRESS "Address"
1400 #define SLAVE_VERSION "Version"
1401 #define SLAVE_STATUS "Status"
1402 #define SLAVE_SEEN "Last Seen"
1405 init_stats_names(krb5_context context
)
1407 const char *fn
= NULL
;
1410 if (slave_stats_file
)
1411 fn
= slave_stats_file
;
1412 else if ((fn
= krb5_config_get_string(context
, NULL
, "kdc",
1413 "iprop-stats", NULL
)) == NULL
) {
1414 if (asprintf(&buf
, "%s/slaves-stats", hdb_db_dir(context
)) != -1
1420 slave_stats_file
= fn
;
1421 if (asprintf(&buf
, "%s.tmp", fn
) != -1 && buf
!= NULL
)
1422 slave_stats_temp_file
= buf
;
1427 write_master_down(krb5_context context
)
1430 time_t t
= time(NULL
);
1433 if (slave_stats_temp_file
!= NULL
)
1434 fp
= fopen(slave_stats_temp_file
, "w");
1437 if (krb5_format_time(context
, t
, str
, sizeof(str
), TRUE
) == 0)
1438 fprintf(fp
, "master down at %s\n", str
);
1440 fprintf(fp
, "master down\n");
1442 if (fclose(fp
) != EOF
)
1443 (void) rk_rename(slave_stats_temp_file
, slave_stats_file
);
1447 write_stats(krb5_context context
, slave
*slaves
, uint32_t current_version
)
1451 time_t t
= time(NULL
);
1454 if (slave_stats_temp_file
!= NULL
)
1455 fp
= fopen(slave_stats_temp_file
, "w");
1459 if (krb5_format_time(context
, t
, str
, sizeof(str
), TRUE
))
1460 snprintf(str
, sizeof(str
), "<unknown-time>");
1461 fprintf(fp
, "Status for slaves, last updated: %s\n\n", str
);
1463 fprintf(fp
, "Master version: %lu\n\n", (unsigned long)current_version
);
1465 tbl
= rtbl_create();
1471 rtbl_add_column(tbl
, SLAVE_NAME
, 0);
1472 rtbl_add_column(tbl
, SLAVE_ADDRESS
, 0);
1473 rtbl_add_column(tbl
, SLAVE_VERSION
, RTBL_ALIGN_RIGHT
);
1474 rtbl_add_column(tbl
, SLAVE_STATUS
, 0);
1475 rtbl_add_column(tbl
, SLAVE_SEEN
, 0);
1477 rtbl_set_prefix(tbl
, " ");
1478 rtbl_set_column_prefix(tbl
, SLAVE_NAME
, "");
1482 krb5_error_code ret
;
1483 rtbl_add_column_entry(tbl
, SLAVE_NAME
, slaves
->name
);
1484 ret
= krb5_sockaddr2address (context
,
1485 (struct sockaddr
*)&slaves
->addr
, &addr
);
1487 krb5_print_address(&addr
, str
, sizeof(str
), NULL
);
1488 krb5_free_address(context
, &addr
);
1489 rtbl_add_column_entry(tbl
, SLAVE_ADDRESS
, str
);
1491 rtbl_add_column_entry(tbl
, SLAVE_ADDRESS
, "<unknown>");
1493 snprintf(str
, sizeof(str
), "%u", (unsigned)slaves
->version_ack
);
1494 rtbl_add_column_entry(tbl
, SLAVE_VERSION
, str
);
1496 if (slaves
->flags
& SLAVE_F_DEAD
)
1497 rtbl_add_column_entry(tbl
, SLAVE_STATUS
, "Down");
1499 rtbl_add_column_entry(tbl
, SLAVE_STATUS
, "Up");
1501 ret
= krb5_format_time(context
, slaves
->seen
, str
, sizeof(str
), TRUE
);
1503 rtbl_add_column_entry(tbl
, SLAVE_SEEN
, "<error-formatting-time>");
1505 rtbl_add_column_entry(tbl
, SLAVE_SEEN
, str
);
1507 slaves
= slaves
->next
;
1510 rtbl_format(tbl
, fp
);
1513 if (fclose(fp
) != EOF
)
1514 (void) rk_rename(slave_stats_temp_file
, slave_stats_file
);
1518 static char sHDB
[] = "HDBGET:";
1520 static int version_flag
;
1521 static int help_flag
;
1522 static char *keytab_str
= sHDB
;
1523 static char *database
;
1524 static char *config_file
;
1525 static char *port_str
;
1526 static int detach_from_console
;
1527 static int daemon_child
= -1;
1529 static struct getargs args
[] = {
1530 { "config-file", 'c', arg_string
, &config_file
, NULL
, NULL
},
1531 { "realm", 'r', arg_string
, &realm
, NULL
, NULL
},
1532 { "keytab", 'k', arg_string
, &keytab_str
,
1533 "keytab to get authentication from", "kspec" },
1534 { "database", 'd', arg_string
, &database
, "database", "file"},
1535 { "slave-stats-file", 0, arg_string
, rk_UNCONST(&slave_stats_file
),
1536 "file for slave status information", "file"},
1537 { "time-missing", 0, arg_string
, rk_UNCONST(&slave_time_missing
),
1538 "time before slave is polled for presence", "time"},
1539 { "time-gone", 0, arg_string
, rk_UNCONST(&slave_time_gone
),
1540 "time of inactivity after which a slave is considered gone", "time"},
1541 { "port", 0, arg_string
, &port_str
,
1542 "port ipropd will listen to", "port"},
1543 { "detach", 0, arg_flag
, &detach_from_console
,
1544 "detach from console", NULL
},
1545 { "daemon-child", 0, arg_integer
, &daemon_child
,
1546 "private argument, do not use", NULL
},
1547 { "pidfile-basename", 0, arg_string
, &pidfile_basename
,
1548 "basename of pidfile; private argument for testing", "NAME" },
1549 { "hostname", 0, arg_string
, rk_UNCONST(&master_hostname
),
1550 "hostname of master (if not same as hostname)", "hostname" },
1551 { "verbose", 0, arg_flag
, &verbose
, NULL
, NULL
},
1552 { "version", 0, arg_flag
, &version_flag
, NULL
, NULL
},
1553 { "help", 0, arg_flag
, &help_flag
, NULL
, NULL
}
1555 static int num_args
= sizeof(args
) / sizeof(args
[0]);
1558 main(int argc
, char **argv
)
1560 krb5_error_code ret
;
1561 krb5_context context
;
1563 kadm5_server_context
*server_context
;
1564 kadm5_config_params conf
;
1565 krb5_socket_t signal_fd
, listen_fd
;
1567 slave
*slaves
= NULL
;
1568 uint32_t current_version
= 0, old_version
= 0;
1573 int restarter_fd
= -1;
1576 setprogname(argv
[0]);
1578 if (getarg(args
, num_args
, argc
, argv
, &optidx
))
1579 krb5_std_usage(1, args
, num_args
);
1582 krb5_std_usage(0, args
, num_args
);
1585 print_version(NULL
);
1589 memset(hostname
, 0, sizeof(hostname
));
1591 if (master_hostname
&&
1592 strlcpy(hostname
, master_hostname
,
1593 sizeof(hostname
)) >= sizeof(hostname
)) {
1594 errx(1, "Hostname too long: %s", master_hostname
);
1595 } else if (master_hostname
== NULL
) {
1596 if (gethostname(hostname
, sizeof(hostname
)) == -1)
1597 err(1, "Could not get hostname");
1598 if (hostname
[sizeof(hostname
) - 1] != '\0')
1599 errx(1, "Hostname too long %.*s...",
1600 (int)sizeof(hostname
), hostname
);
1603 if (detach_from_console
&& daemon_child
== -1)
1604 daemon_child
= roken_detach_prep(argc
, argv
, "--daemon-child");
1605 rk_pidfile(pidfile_basename
);
1607 ret
= krb5_init_context(&context
);
1609 errx(1, "krb5_init_context failed: %d", ret
);
1613 if (config_file
== NULL
) {
1614 aret
= asprintf(&config_file
, "%s/kdc.conf", hdb_db_dir(context
));
1615 if (aret
== -1 || config_file
== NULL
)
1616 errx(1, "out of memory");
1619 ret
= krb5_prepend_config_files_default(config_file
, &files
);
1621 krb5_err(context
, 1, ret
, "getting configuration files");
1623 ret
= krb5_set_config_files(context
, files
);
1624 krb5_free_config_files(files
);
1626 krb5_err(context
, 1, ret
, "reading configuration files");
1628 init_stats_names(context
);
1630 time_before_gone
= parse_time (slave_time_gone
, "s");
1631 if (time_before_gone
< 0)
1632 krb5_errx (context
, 1, "couldn't parse time: %s", slave_time_gone
);
1633 time_before_missing
= parse_time (slave_time_missing
, "s");
1634 if (time_before_missing
< 0)
1635 krb5_errx (context
, 1, "couldn't parse time: %s", slave_time_missing
);
1637 krb5_openlog(context
, "ipropd-master", &log_facility
);
1638 krb5_set_warn_dest(context
, log_facility
);
1640 ret
= krb5_kt_register(context
, &hdb_get_kt_ops
);
1642 krb5_err(context
, 1, ret
, "krb5_kt_register");
1644 ret
= krb5_kt_resolve(context
, keytab_str
, &keytab
);
1646 krb5_err(context
, 1, ret
, "krb5_kt_resolve: %s", keytab_str
);
1648 memset(&conf
, 0, sizeof(conf
));
1650 conf
.mask
|= KADM5_CONFIG_REALM
;
1653 ret
= kadm5_init_with_skey_ctx (context
,
1654 KADM5_ADMIN_SERVICE
,
1656 KADM5_ADMIN_SERVICE
,
1660 krb5_err (context
, 1, ret
, "kadm5_init_with_password_ctx");
1662 server_context
= (kadm5_server_context
*)kadm_handle
;
1664 log_fd
= open (server_context
->log_context
.log_file
, O_RDONLY
, 0);
1666 krb5_err (context
, 1, errno
, "open %s",
1667 server_context
->log_context
.log_file
);
1669 if (fstat(log_fd
, &st
) == -1)
1670 krb5_err(context
, 1, errno
, "stat %s",
1671 server_context
->log_context
.log_file
);
1673 if (flock(log_fd
, LOCK_SH
) == -1)
1674 krb5_err(context
, 1, errno
, "shared flock %s",
1675 server_context
->log_context
.log_file
);
1676 kadm5_log_get_version_fd(server_context
, log_fd
, LOG_VERSION_LAST
,
1677 ¤t_version
, NULL
);
1678 flock(log_fd
, LOCK_UN
);
1680 signal_fd
= make_signal_socket (context
);
1681 listen_fd
= make_listen_socket (context
, port_str
);
1683 krb5_warnx(context
, "ipropd-master started at version: %lu",
1684 (unsigned long)current_version
);
1686 roken_detach_finish(NULL
, daemon_child
);
1687 restarter_fd
= restarter(context
, NULL
);
1689 while (exit_flag
== 0){
1691 fd_set readset
, writeset
;
1693 struct timeval to
= {30, 0};
1697 #ifndef NO_LIMIT_FD_SETSIZE
1698 if (signal_fd
>= FD_SETSIZE
|| listen_fd
>= FD_SETSIZE
||
1699 restarter_fd
>= FD_SETSIZE
)
1700 krb5_errx (context
, IPROPD_RESTART
, "fd too large");
1705 FD_SET(signal_fd
, &readset
);
1706 max_fd
= max(max_fd
, signal_fd
);
1707 FD_SET(listen_fd
, &readset
);
1708 max_fd
= max(max_fd
, listen_fd
);
1709 if (restarter_fd
> -1) {
1710 FD_SET(restarter_fd
, &readset
);
1711 max_fd
= max(max_fd
, restarter_fd
);
1714 for (p
= slaves
; p
!= NULL
; p
= p
->next
) {
1715 if (p
->flags
& SLAVE_F_DEAD
)
1717 FD_SET(p
->fd
, &readset
);
1718 if (have_tail(p
) || more_diffs(p
))
1719 FD_SET(p
->fd
, &writeset
);
1720 max_fd
= max(max_fd
, p
->fd
);
1723 ret
= select(max_fd
+ 1, &readset
, &writeset
, NULL
, &to
);
1728 krb5_err (context
, IPROPD_RESTART
, errno
, "select");
1731 if (stat(server_context
->log_context
.log_file
, &st2
) == -1) {
1732 krb5_warn(context
, errno
, "could not stat log file by path");
1736 if (st2
.st_dev
!= st
.st_dev
|| st2
.st_ino
!= st
.st_ino
) {
1737 (void) close(log_fd
);
1739 log_fd
= open(server_context
->log_context
.log_file
, O_RDONLY
, 0);
1741 krb5_err(context
, IPROPD_RESTART_SLOW
, errno
, "open %s",
1742 server_context
->log_context
.log_file
);
1744 if (fstat(log_fd
, &st
) == -1)
1745 krb5_err(context
, IPROPD_RESTART_SLOW
, errno
, "stat %s",
1746 server_context
->log_context
.log_file
);
1748 if (flock(log_fd
, LOCK_SH
) == -1)
1749 krb5_err(context
, IPROPD_RESTART
, errno
, "shared flock %s",
1750 server_context
->log_context
.log_file
);
1751 kadm5_log_get_version_fd(server_context
, log_fd
, LOG_VERSION_LAST
,
1752 ¤t_version
, NULL
);
1753 flock(log_fd
, LOCK_UN
);
1757 /* Recover from failed transactions */
1758 if (kadm5_log_init_nb(server_context
) == 0)
1759 kadm5_log_end(server_context
);
1761 if (flock(log_fd
, LOCK_SH
) == -1)
1762 krb5_err(context
, IPROPD_RESTART
, errno
,
1763 "could not lock log file");
1764 kadm5_log_get_version_fd(server_context
, log_fd
, LOG_VERSION_LAST
,
1765 ¤t_version
, NULL
);
1766 flock(log_fd
, LOCK_UN
);
1768 if (current_version
> old_version
) {
1771 "Missed a signal, updating slaves %lu to %lu",
1772 (unsigned long)old_version
,
1773 (unsigned long)current_version
);
1774 for (p
= slaves
; p
!= NULL
; p
= p
->next
) {
1775 if (p
->flags
& SLAVE_F_DEAD
)
1777 send_diffs(server_context
, p
, log_fd
, database
,
1780 old_version
= current_version
;
1784 if (ret
&& FD_ISSET(restarter_fd
, &readset
)) {
1785 exit_flag
= SIGTERM
;
1789 if (ret
&& FD_ISSET(signal_fd
, &readset
)) {
1790 #ifndef NO_UNIX_SOCKETS
1791 struct sockaddr_un peer_addr
;
1793 struct sockaddr_storage peer_addr
;
1795 socklen_t peer_len
= sizeof(peer_addr
);
1797 if(recvfrom(signal_fd
, (void *)&vers
, sizeof(vers
), 0,
1798 (struct sockaddr
*)&peer_addr
, &peer_len
) < 0) {
1799 krb5_warn (context
, errno
, "recvfrom");
1804 old_version
= current_version
;
1805 if (flock(log_fd
, LOCK_SH
) == -1)
1806 krb5_err(context
, IPROPD_RESTART
, errno
, "shared flock %s",
1807 server_context
->log_context
.log_file
);
1808 kadm5_log_get_version_fd(server_context
, log_fd
, LOG_VERSION_LAST
,
1809 ¤t_version
, NULL
);
1810 flock(log_fd
, LOCK_UN
);
1811 if (current_version
!= old_version
) {
1813 * If current_version < old_version then the log got
1814 * truncated and we'll end up doing full propagations.
1816 * Truncating the log when the current version is
1817 * numerically small can lead to race conditions.
1818 * Ideally we should identify log versions as
1819 * {init_or_trunc_time, vno}, then we could not have any
1820 * such race conditions, but this would either require
1821 * breaking backwards compatibility for the protocol or
1822 * adding new messages to it.
1826 "Got a signal, updating slaves %lu to %lu",
1827 (unsigned long)old_version
,
1828 (unsigned long)current_version
);
1829 for (p
= slaves
; p
!= NULL
; p
= p
->next
) {
1830 if (p
->flags
& SLAVE_F_DEAD
)
1832 send_diffs(server_context
, p
, log_fd
, database
,
1838 "Got a signal, but no update in log version %lu",
1839 (unsigned long)current_version
);
1843 for (p
= slaves
; p
!= NULL
; p
= p
->next
) {
1844 if (!(p
->flags
& SLAVE_F_DEAD
) &&
1845 FD_ISSET(p
->fd
, &writeset
) &&
1846 ((have_tail(p
) && send_tail(context
, p
) == 0) ||
1847 (!have_tail(p
) && more_diffs(p
)))) {
1848 send_diffs(server_context
, p
, log_fd
, database
,
1853 for(p
= slaves
; p
!= NULL
; p
= p
->next
) {
1854 if (p
->flags
& SLAVE_F_DEAD
)
1856 if (ret
&& FD_ISSET(p
->fd
, &readset
)) {
1859 ret
= process_msg(server_context
, p
, log_fd
, database
,
1861 if (ret
&& ret
!= EWOULDBLOCK
)
1862 slave_dead(context
, p
);
1863 } else if (slave_gone_p (p
))
1864 slave_dead(context
, p
);
1865 else if (slave_missing_p (p
))
1866 send_are_you_there (context
, p
);
1869 if (ret
&& FD_ISSET(listen_fd
, &readset
)) {
1870 add_slave (context
, keytab
, &slaves
, listen_fd
);
1874 write_stats(context
, slaves
, current_version
);
1877 if(exit_flag
== SIGINT
|| exit_flag
== SIGTERM
)
1878 krb5_warnx(context
, "%s terminated", getprogname());
1880 else if(exit_flag
== SIGXCPU
)
1881 krb5_warnx(context
, "%s CPU time limit exceeded", getprogname());
1884 krb5_warnx(context
, "%s unexpected exit reason: %ld",
1885 getprogname(), (long)exit_flag
);
1887 write_master_down(context
);