
We turn on a few extra warnings and fix the fallout that occurs when building with --enable-developer. Note that we get different warnings on different machines and so this will be a work in progress. So far, we have built on NetBSD/amd64 5.99.64 (which uses gcc 4.5.3) and Ubuntu 10.04.3 LTS (which uses gcc 4.4.3). Notably, we fixed 1. a lot of missing structure initialisers, 2. unchecked return values for functions that glibc marks as __attribute__((warn-unused-result)), 3. made minor modifications to slc and asn1_compile which can generate code which generates warnings, and 4. a few stragglers here and there. We turned off the extended warnings for many programs in appl/ as they are nearing the end of their useful lifetime, e.g. rsh, rcp, popper, ftp and telnet. Interestingly, glibc's strncmp() macro needed to be worked around whereas the function calls did not. We have not yet tried this on 32 bit platforms, so there will be a few more warnings when we do.
986 lines
25 KiB
C
986 lines
25 KiB
C
/*
|
|
* Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
|
|
* (Royal Institute of Technology, Stockholm, Sweden).
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the Institute nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "iprop.h"
|
|
#include <rtbl.h>
|
|
|
|
static krb5_log_facility *log_facility;
|
|
|
|
const char *slave_stats_file;
|
|
const char *slave_time_missing = "2 min";
|
|
const char *slave_time_gone = "5 min";
|
|
|
|
static int time_before_missing;
|
|
static int time_before_gone;
|
|
|
|
const char *master_hostname;
|
|
|
|
static krb5_socket_t
|
|
make_signal_socket (krb5_context context)
|
|
{
|
|
#ifndef NO_UNIX_SOCKETS
|
|
struct sockaddr_un addr;
|
|
const char *fn;
|
|
krb5_socket_t fd;
|
|
|
|
fn = kadm5_log_signal_socket(context);
|
|
|
|
fd = socket (AF_UNIX, SOCK_DGRAM, 0);
|
|
if (fd < 0)
|
|
krb5_err (context, 1, errno, "socket AF_UNIX");
|
|
memset (&addr, 0, sizeof(addr));
|
|
addr.sun_family = AF_UNIX;
|
|
strlcpy (addr.sun_path, fn, sizeof(addr.sun_path));
|
|
unlink (addr.sun_path);
|
|
if (bind (fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
|
|
krb5_err (context, 1, errno, "bind %s", addr.sun_path);
|
|
return fd;
|
|
#else
|
|
struct addrinfo *ai = NULL;
|
|
krb5_socket_t fd;
|
|
|
|
kadm5_log_signal_socket_info(context, 1, &ai);
|
|
|
|
fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
|
|
if (rk_IS_BAD_SOCKET(fd))
|
|
krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF=%d", ai->ai_family);
|
|
|
|
if (rk_IS_SOCKET_ERROR( bind (fd, ai->ai_addr, ai->ai_addrlen) ))
|
|
krb5_err (context, 1, rk_SOCK_ERRNO, "bind");
|
|
return fd;
|
|
#endif
|
|
}
|
|
|
|
static krb5_socket_t
|
|
make_listen_socket (krb5_context context, const char *port_str)
|
|
{
|
|
krb5_socket_t fd;
|
|
int one = 1;
|
|
struct sockaddr_in addr;
|
|
|
|
fd = socket (AF_INET, SOCK_STREAM, 0);
|
|
if (rk_IS_BAD_SOCKET(fd))
|
|
krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF_INET");
|
|
setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
|
|
memset (&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
|
|
if (port_str) {
|
|
addr.sin_port = krb5_getportbyname (context,
|
|
port_str, "tcp",
|
|
0);
|
|
if (addr.sin_port == 0) {
|
|
char *ptr;
|
|
long port;
|
|
|
|
port = strtol (port_str, &ptr, 10);
|
|
if (port == 0 && ptr == port_str)
|
|
krb5_errx (context, 1, "bad port `%s'", port_str);
|
|
addr.sin_port = htons(port);
|
|
}
|
|
} else {
|
|
addr.sin_port = krb5_getportbyname (context, IPROP_SERVICE,
|
|
"tcp", IPROP_PORT);
|
|
}
|
|
if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
|
|
krb5_err (context, 1, errno, "bind");
|
|
if (listen(fd, SOMAXCONN) < 0)
|
|
krb5_err (context, 1, errno, "listen");
|
|
return fd;
|
|
}
|
|
|
|
struct slave {
|
|
krb5_socket_t fd;
|
|
struct sockaddr_in addr;
|
|
char *name;
|
|
krb5_auth_context ac;
|
|
uint32_t version;
|
|
time_t seen;
|
|
unsigned long flags;
|
|
#define SLAVE_F_DEAD 0x1
|
|
#define SLAVE_F_AYT 0x2
|
|
struct slave *next;
|
|
};
|
|
|
|
typedef struct slave slave;
|
|
|
|
static int
|
|
check_acl (krb5_context context, const char *name)
|
|
{
|
|
const char *fn;
|
|
FILE *fp;
|
|
char buf[256];
|
|
int ret = 1;
|
|
char *slavefile = NULL;
|
|
|
|
if (asprintf(&slavefile, "%s/slaves", hdb_db_dir(context)) == -1
|
|
|| slavefile == NULL)
|
|
errx(1, "out of memory");
|
|
|
|
fn = krb5_config_get_string_default(context,
|
|
NULL,
|
|
slavefile,
|
|
"kdc",
|
|
"iprop-acl",
|
|
NULL);
|
|
|
|
fp = fopen (fn, "r");
|
|
free(slavefile);
|
|
if (fp == NULL)
|
|
return 1;
|
|
while (fgets(buf, sizeof(buf), fp) != NULL) {
|
|
buf[strcspn(buf, "\r\n")] = '\0';
|
|
if (strcmp (buf, name) == 0) {
|
|
ret = 0;
|
|
break;
|
|
}
|
|
}
|
|
fclose (fp);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
slave_seen(slave *s)
|
|
{
|
|
s->flags &= ~SLAVE_F_AYT;
|
|
s->seen = time(NULL);
|
|
}
|
|
|
|
static int
|
|
slave_missing_p (slave *s)
|
|
{
|
|
if (time(NULL) > s->seen + time_before_missing)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
slave_gone_p (slave *s)
|
|
{
|
|
if (time(NULL) > s->seen + time_before_gone)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
slave_dead(krb5_context context, slave *s)
|
|
{
|
|
krb5_warnx(context, "slave %s dead", s->name);
|
|
|
|
if (!rk_IS_BAD_SOCKET(s->fd)) {
|
|
rk_closesocket (s->fd);
|
|
s->fd = rk_INVALID_SOCKET;
|
|
}
|
|
s->flags |= SLAVE_F_DEAD;
|
|
slave_seen(s);
|
|
}
|
|
|
|
static void
|
|
remove_slave (krb5_context context, slave *s, slave **root)
|
|
{
|
|
slave **p;
|
|
|
|
if (!rk_IS_BAD_SOCKET(s->fd))
|
|
rk_closesocket (s->fd);
|
|
if (s->name)
|
|
free (s->name);
|
|
if (s->ac)
|
|
krb5_auth_con_free (context, s->ac);
|
|
|
|
for (p = root; *p; p = &(*p)->next)
|
|
if (*p == s) {
|
|
*p = s->next;
|
|
break;
|
|
}
|
|
free (s);
|
|
}
|
|
|
|
static void
|
|
add_slave (krb5_context context, krb5_keytab keytab, slave **root,
|
|
krb5_socket_t fd)
|
|
{
|
|
krb5_principal server;
|
|
krb5_error_code ret;
|
|
slave *s;
|
|
socklen_t addr_len;
|
|
krb5_ticket *ticket = NULL;
|
|
char hostname[128];
|
|
|
|
s = malloc(sizeof(*s));
|
|
if (s == NULL) {
|
|
krb5_warnx (context, "add_slave: no memory");
|
|
return;
|
|
}
|
|
s->name = NULL;
|
|
s->ac = NULL;
|
|
|
|
addr_len = sizeof(s->addr);
|
|
s->fd = accept (fd, (struct sockaddr *)&s->addr, &addr_len);
|
|
if (rk_IS_BAD_SOCKET(s->fd)) {
|
|
krb5_warn (context, rk_SOCK_ERRNO, "accept");
|
|
goto error;
|
|
}
|
|
if (master_hostname)
|
|
strlcpy(hostname, master_hostname, sizeof(hostname));
|
|
else
|
|
gethostname(hostname, sizeof(hostname));
|
|
|
|
ret = krb5_sname_to_principal (context, hostname, IPROP_NAME,
|
|
KRB5_NT_SRV_HST, &server);
|
|
if (ret) {
|
|
krb5_warn (context, ret, "krb5_sname_to_principal");
|
|
goto error;
|
|
}
|
|
|
|
ret = krb5_recvauth (context, &s->ac, &s->fd,
|
|
IPROP_VERSION, server, 0, keytab, &ticket);
|
|
krb5_free_principal (context, server);
|
|
if (ret) {
|
|
krb5_warn (context, ret, "krb5_recvauth");
|
|
goto error;
|
|
}
|
|
ret = krb5_unparse_name (context, ticket->client, &s->name);
|
|
krb5_free_ticket (context, ticket);
|
|
if (ret) {
|
|
krb5_warn (context, ret, "krb5_unparse_name");
|
|
goto error;
|
|
}
|
|
if (check_acl (context, s->name)) {
|
|
krb5_warnx (context, "%s not in acl", s->name);
|
|
goto error;
|
|
}
|
|
|
|
{
|
|
slave *l = *root;
|
|
|
|
while (l) {
|
|
if (strcmp(l->name, s->name) == 0)
|
|
break;
|
|
l = l->next;
|
|
}
|
|
if (l) {
|
|
if (l->flags & SLAVE_F_DEAD) {
|
|
remove_slave(context, l, root);
|
|
} else {
|
|
krb5_warnx (context, "second connection from %s", s->name);
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
krb5_warnx (context, "connection from %s", s->name);
|
|
|
|
s->version = 0;
|
|
s->flags = 0;
|
|
slave_seen(s);
|
|
s->next = *root;
|
|
*root = s;
|
|
return;
|
|
error:
|
|
remove_slave(context, s, root);
|
|
}
|
|
|
|
static int
|
|
prop_one (krb5_context context, HDB *db, hdb_entry_ex *entry, void *v)
|
|
{
|
|
krb5_error_code ret;
|
|
krb5_storage *sp;
|
|
krb5_data data;
|
|
struct slave *s = (struct slave *)v;
|
|
|
|
ret = hdb_entry2value (context, &entry->entry, &data);
|
|
if (ret)
|
|
return ret;
|
|
ret = krb5_data_realloc (&data, data.length + 4);
|
|
if (ret) {
|
|
krb5_data_free (&data);
|
|
return ret;
|
|
}
|
|
memmove ((char *)data.data + 4, data.data, data.length - 4);
|
|
sp = krb5_storage_from_data(&data);
|
|
if (sp == NULL) {
|
|
krb5_data_free (&data);
|
|
return ENOMEM;
|
|
}
|
|
krb5_store_int32(sp, ONE_PRINC);
|
|
krb5_storage_free(sp);
|
|
|
|
ret = krb5_write_priv_message (context, s->ac, &s->fd, &data);
|
|
krb5_data_free (&data);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
send_complete (krb5_context context, slave *s,
|
|
const char *database, uint32_t current_version)
|
|
{
|
|
krb5_error_code ret;
|
|
krb5_storage *sp;
|
|
HDB *db;
|
|
krb5_data data;
|
|
char buf[8];
|
|
|
|
ret = hdb_create (context, &db, database);
|
|
if (ret)
|
|
krb5_err (context, 1, ret, "hdb_create: %s", database);
|
|
ret = db->hdb_open (context, db, O_RDONLY, 0);
|
|
if (ret)
|
|
krb5_err (context, 1, ret, "db->open");
|
|
|
|
sp = krb5_storage_from_mem (buf, 4);
|
|
if (sp == NULL)
|
|
krb5_errx (context, 1, "krb5_storage_from_mem");
|
|
krb5_store_int32 (sp, TELL_YOU_EVERYTHING);
|
|
krb5_storage_free (sp);
|
|
|
|
data.data = buf;
|
|
data.length = 4;
|
|
|
|
ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
|
|
|
|
if (ret) {
|
|
krb5_warn (context, ret, "krb5_write_priv_message");
|
|
slave_dead(context, s);
|
|
return ret;
|
|
}
|
|
|
|
ret = hdb_foreach (context, db, HDB_F_ADMIN_DATA, prop_one, s);
|
|
if (ret) {
|
|
krb5_warn (context, ret, "hdb_foreach");
|
|
slave_dead(context, s);
|
|
return ret;
|
|
}
|
|
|
|
(*db->hdb_close)(context, db);
|
|
(*db->hdb_destroy)(context, db);
|
|
|
|
sp = krb5_storage_from_mem (buf, 8);
|
|
if (sp == NULL)
|
|
krb5_errx (context, 1, "krb5_storage_from_mem");
|
|
krb5_store_int32 (sp, NOW_YOU_HAVE);
|
|
krb5_store_int32 (sp, current_version);
|
|
krb5_storage_free (sp);
|
|
|
|
data.length = 8;
|
|
|
|
s->version = current_version;
|
|
|
|
ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
|
|
if (ret) {
|
|
slave_dead(context, s);
|
|
krb5_warn (context, ret, "krb5_write_priv_message");
|
|
return ret;
|
|
}
|
|
|
|
slave_seen(s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
send_are_you_there (krb5_context context, slave *s)
|
|
{
|
|
krb5_storage *sp;
|
|
krb5_data data;
|
|
char buf[4];
|
|
int ret;
|
|
|
|
if (s->flags & (SLAVE_F_DEAD|SLAVE_F_AYT))
|
|
return 0;
|
|
|
|
krb5_warnx(context, "slave %s missing, sending AYT", s->name);
|
|
|
|
s->flags |= SLAVE_F_AYT;
|
|
|
|
data.data = buf;
|
|
data.length = 4;
|
|
|
|
sp = krb5_storage_from_mem (buf, 4);
|
|
if (sp == NULL) {
|
|
krb5_warnx (context, "are_you_there: krb5_data_alloc");
|
|
slave_dead(context, s);
|
|
return 1;
|
|
}
|
|
krb5_store_int32 (sp, ARE_YOU_THERE);
|
|
krb5_storage_free (sp);
|
|
|
|
ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
|
|
|
|
if (ret) {
|
|
krb5_warn (context, ret, "are_you_there: krb5_write_priv_message");
|
|
slave_dead(context, s);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
send_diffs (krb5_context context, slave *s, int log_fd,
|
|
const char *database, uint32_t current_version)
|
|
{
|
|
krb5_storage *sp;
|
|
uint32_t ver;
|
|
time_t timestamp;
|
|
enum kadm_ops op;
|
|
uint32_t len;
|
|
off_t right, left;
|
|
krb5_data data;
|
|
int ret = 0;
|
|
|
|
if (s->version == current_version) {
|
|
char buf[4];
|
|
|
|
sp = krb5_storage_from_mem(buf, 4);
|
|
if (sp == NULL)
|
|
krb5_errx(context, 1, "krb5_storage_from_mem");
|
|
krb5_store_int32(sp, YOU_HAVE_LAST_VERSION);
|
|
krb5_storage_free(sp);
|
|
data.data = buf;
|
|
data.length = 4;
|
|
ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
|
|
krb5_warnx(context, "slave %s in sync already at version %ld",
|
|
s->name, (long)s->version);
|
|
return ret;
|
|
}
|
|
|
|
if (s->flags & SLAVE_F_DEAD)
|
|
return 0;
|
|
|
|
/* if slave is a fresh client, starting over */
|
|
if (s->version == 0) {
|
|
krb5_warnx(context, "sending complete log to fresh slave %s",
|
|
s->name);
|
|
return send_complete (context, s, database, current_version);
|
|
}
|
|
|
|
sp = kadm5_log_goto_end (log_fd);
|
|
right = krb5_storage_seek(sp, 0, SEEK_CUR);
|
|
for (;;) {
|
|
ret = kadm5_log_previous (context, sp, &ver, ×tamp, &op, &len);
|
|
if (ret)
|
|
krb5_err(context, 1, ret,
|
|
"send_diffs: failed to find previous entry");
|
|
left = krb5_storage_seek(sp, -16, SEEK_CUR);
|
|
if (ver == s->version)
|
|
return 0;
|
|
if (ver == s->version + 1)
|
|
break;
|
|
if (left == 0) {
|
|
krb5_storage_free(sp);
|
|
krb5_warnx(context,
|
|
"slave %s (version %lu) out of sync with master "
|
|
"(first version in log %lu), sending complete database",
|
|
s->name, (unsigned long)s->version, (unsigned long)ver);
|
|
return send_complete (context, s, database, current_version);
|
|
}
|
|
}
|
|
|
|
krb5_warnx(context,
|
|
"syncing slave %s from version %lu to version %lu",
|
|
s->name, (unsigned long)s->version,
|
|
(unsigned long)current_version);
|
|
|
|
ret = krb5_data_alloc (&data, right - left + 4);
|
|
if (ret) {
|
|
krb5_storage_free(sp);
|
|
krb5_warn (context, ret, "send_diffs: krb5_data_alloc");
|
|
slave_dead(context, s);
|
|
return 1;
|
|
}
|
|
krb5_storage_read (sp, (char *)data.data + 4, data.length - 4);
|
|
krb5_storage_free(sp);
|
|
|
|
sp = krb5_storage_from_data (&data);
|
|
if (sp == NULL) {
|
|
krb5_warnx (context, "send_diffs: krb5_storage_from_data");
|
|
slave_dead(context, s);
|
|
return 1;
|
|
}
|
|
krb5_store_int32 (sp, FOR_YOU);
|
|
krb5_storage_free(sp);
|
|
|
|
ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
|
|
krb5_data_free(&data);
|
|
|
|
if (ret) {
|
|
krb5_warn (context, ret, "send_diffs: krb5_write_priv_message");
|
|
slave_dead(context, s);
|
|
return 1;
|
|
}
|
|
slave_seen(s);
|
|
|
|
s->version = current_version;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
process_msg (krb5_context context, slave *s, int log_fd,
|
|
const char *database, uint32_t current_version)
|
|
{
|
|
int ret = 0;
|
|
krb5_data out;
|
|
krb5_storage *sp;
|
|
int32_t tmp;
|
|
|
|
ret = krb5_read_priv_message(context, s->ac, &s->fd, &out);
|
|
if(ret) {
|
|
krb5_warn (context, ret, "error reading message from %s", s->name);
|
|
return 1;
|
|
}
|
|
|
|
sp = krb5_storage_from_mem (out.data, out.length);
|
|
if (sp == NULL) {
|
|
krb5_warnx (context, "process_msg: no memory");
|
|
krb5_data_free (&out);
|
|
return 1;
|
|
}
|
|
if (krb5_ret_int32 (sp, &tmp) != 0) {
|
|
krb5_warnx (context, "process_msg: client send too short command");
|
|
krb5_data_free (&out);
|
|
return 1;
|
|
}
|
|
switch (tmp) {
|
|
case I_HAVE :
|
|
ret = krb5_ret_int32 (sp, &tmp);
|
|
if (ret != 0) {
|
|
krb5_warnx (context, "process_msg: client send too I_HAVE data");
|
|
break;
|
|
}
|
|
/* new started slave that have old log */
|
|
if (s->version == 0 && tmp != 0) {
|
|
if (current_version < (uint32_t)tmp) {
|
|
krb5_warnx (context, "Slave %s (version %lu) have later version "
|
|
"the master (version %lu) OUT OF SYNC",
|
|
s->name, (unsigned long)tmp,
|
|
(unsigned long)current_version);
|
|
}
|
|
s->version = tmp;
|
|
}
|
|
if ((uint32_t)tmp < s->version) {
|
|
krb5_warnx (context, "Slave claims to not have "
|
|
"version we already sent to it");
|
|
} else {
|
|
ret = send_diffs (context, s, log_fd, database, current_version);
|
|
}
|
|
break;
|
|
case I_AM_HERE :
|
|
break;
|
|
case ARE_YOU_THERE:
|
|
case FOR_YOU :
|
|
default :
|
|
krb5_warnx (context, "Ignoring command %d", tmp);
|
|
break;
|
|
}
|
|
|
|
krb5_data_free (&out);
|
|
krb5_storage_free (sp);
|
|
|
|
slave_seen(s);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define SLAVE_NAME "Name"
|
|
#define SLAVE_ADDRESS "Address"
|
|
#define SLAVE_VERSION "Version"
|
|
#define SLAVE_STATUS "Status"
|
|
#define SLAVE_SEEN "Last Seen"
|
|
|
|
static FILE *
|
|
open_stats(krb5_context context)
|
|
{
|
|
char *statfile = NULL;
|
|
const char *fn;
|
|
int ret;
|
|
|
|
if (slave_stats_file)
|
|
fn = slave_stats_file;
|
|
else {
|
|
ret = asprintf(&statfile, "%s/slaves-stats", hdb_db_dir(context));
|
|
if (ret == -1)
|
|
return NULL;
|
|
fn = krb5_config_get_string_default(context,
|
|
NULL,
|
|
statfile,
|
|
"kdc",
|
|
"iprop-stats",
|
|
NULL);
|
|
free(statfile);
|
|
}
|
|
if (fn == NULL)
|
|
return NULL;
|
|
return fopen(fn, "w");
|
|
}
|
|
|
|
static void
|
|
write_master_down(krb5_context context)
|
|
{
|
|
char str[100];
|
|
time_t t = time(NULL);
|
|
FILE *fp;
|
|
|
|
fp = open_stats(context);
|
|
if (fp == NULL)
|
|
return;
|
|
krb5_format_time(context, t, str, sizeof(str), TRUE);
|
|
fprintf(fp, "master down at %s\n", str);
|
|
|
|
fclose(fp);
|
|
}
|
|
|
|
static void
|
|
write_stats(krb5_context context, slave *slaves, uint32_t current_version)
|
|
{
|
|
char str[100];
|
|
rtbl_t tbl;
|
|
time_t t = time(NULL);
|
|
FILE *fp;
|
|
|
|
fp = open_stats(context);
|
|
if (fp == NULL)
|
|
return;
|
|
|
|
krb5_format_time(context, t, str, sizeof(str), TRUE);
|
|
fprintf(fp, "Status for slaves, last updated: %s\n\n", str);
|
|
|
|
fprintf(fp, "Master version: %lu\n\n", (unsigned long)current_version);
|
|
|
|
tbl = rtbl_create();
|
|
if (tbl == NULL) {
|
|
fclose(fp);
|
|
return;
|
|
}
|
|
|
|
rtbl_add_column(tbl, SLAVE_NAME, 0);
|
|
rtbl_add_column(tbl, SLAVE_ADDRESS, 0);
|
|
rtbl_add_column(tbl, SLAVE_VERSION, RTBL_ALIGN_RIGHT);
|
|
rtbl_add_column(tbl, SLAVE_STATUS, 0);
|
|
rtbl_add_column(tbl, SLAVE_SEEN, 0);
|
|
|
|
rtbl_set_prefix(tbl, " ");
|
|
rtbl_set_column_prefix(tbl, SLAVE_NAME, "");
|
|
|
|
while (slaves) {
|
|
krb5_address addr;
|
|
krb5_error_code ret;
|
|
rtbl_add_column_entry(tbl, SLAVE_NAME, slaves->name);
|
|
ret = krb5_sockaddr2address (context,
|
|
(struct sockaddr*)&slaves->addr, &addr);
|
|
if(ret == 0) {
|
|
krb5_print_address(&addr, str, sizeof(str), NULL);
|
|
krb5_free_address(context, &addr);
|
|
rtbl_add_column_entry(tbl, SLAVE_ADDRESS, str);
|
|
} else
|
|
rtbl_add_column_entry(tbl, SLAVE_ADDRESS, "<unknown>");
|
|
|
|
snprintf(str, sizeof(str), "%u", (unsigned)slaves->version);
|
|
rtbl_add_column_entry(tbl, SLAVE_VERSION, str);
|
|
|
|
if (slaves->flags & SLAVE_F_DEAD)
|
|
rtbl_add_column_entry(tbl, SLAVE_STATUS, "Down");
|
|
else
|
|
rtbl_add_column_entry(tbl, SLAVE_STATUS, "Up");
|
|
|
|
ret = krb5_format_time(context, slaves->seen, str, sizeof(str), TRUE);
|
|
rtbl_add_column_entry(tbl, SLAVE_SEEN, str);
|
|
|
|
slaves = slaves->next;
|
|
}
|
|
|
|
rtbl_format(tbl, fp);
|
|
rtbl_destroy(tbl);
|
|
|
|
fclose(fp);
|
|
}
|
|
|
|
|
|
static char sHDB[] = "HDB:";
|
|
static char *realm;
|
|
static int version_flag;
|
|
static int help_flag;
|
|
static char *keytab_str = sHDB;
|
|
static char *database;
|
|
static char *config_file;
|
|
static char *port_str;
|
|
#ifdef SUPPORT_DETACH
|
|
static int detach_from_console = 0;
|
|
#endif
|
|
|
|
static struct getargs args[] = {
|
|
{ "config-file", 'c', arg_string, &config_file, NULL, NULL },
|
|
{ "realm", 'r', arg_string, &realm, NULL, NULL },
|
|
{ "keytab", 'k', arg_string, &keytab_str,
|
|
"keytab to get authentication from", "kspec" },
|
|
{ "database", 'd', arg_string, &database, "database", "file"},
|
|
{ "slave-stats-file", 0, arg_string, rk_UNCONST(&slave_stats_file),
|
|
"file for slave status information", "file"},
|
|
{ "time-missing", 0, arg_string, rk_UNCONST(&slave_time_missing),
|
|
"time before slave is polled for presence", "time"},
|
|
{ "time-gone", 0, arg_string, rk_UNCONST(&slave_time_gone),
|
|
"time of inactivity after which a slave is considered gone", "time"},
|
|
{ "port", 0, arg_string, &port_str,
|
|
"port ipropd will listen to", "port"},
|
|
#ifdef SUPPORT_DETACH
|
|
{ "detach", 0, arg_flag, &detach_from_console,
|
|
"detach from console", NULL },
|
|
#endif
|
|
{ "hostname", 0, arg_string, rk_UNCONST(&master_hostname),
|
|
"hostname of master (if not same as hostname)", "hostname" },
|
|
{ "version", 0, arg_flag, &version_flag, NULL, NULL },
|
|
{ "help", 0, arg_flag, &help_flag, NULL, NULL }
|
|
};
|
|
static int num_args = sizeof(args) / sizeof(args[0]);
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
krb5_error_code ret;
|
|
krb5_context context;
|
|
void *kadm_handle;
|
|
kadm5_server_context *server_context;
|
|
kadm5_config_params conf;
|
|
krb5_socket_t signal_fd, listen_fd;
|
|
int log_fd;
|
|
slave *slaves = NULL;
|
|
uint32_t current_version = 0, old_version = 0;
|
|
krb5_keytab keytab;
|
|
char **files;
|
|
int aret;
|
|
|
|
(void) krb5_program_setup(&context, argc, argv, args, num_args, NULL);
|
|
|
|
if(help_flag)
|
|
krb5_std_usage(0, args, num_args);
|
|
if(version_flag) {
|
|
print_version(NULL);
|
|
exit(0);
|
|
}
|
|
|
|
setup_signal();
|
|
|
|
if (config_file == NULL) {
|
|
aret = asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context));
|
|
if (aret == -1 || config_file == NULL)
|
|
errx(1, "out of memory");
|
|
}
|
|
|
|
ret = krb5_prepend_config_files_default(config_file, &files);
|
|
if (ret)
|
|
krb5_err(context, 1, ret, "getting configuration files");
|
|
|
|
ret = krb5_set_config_files(context, files);
|
|
krb5_free_config_files(files);
|
|
if (ret)
|
|
krb5_err(context, 1, ret, "reading configuration files");
|
|
|
|
time_before_gone = parse_time (slave_time_gone, "s");
|
|
if (time_before_gone < 0)
|
|
krb5_errx (context, 1, "couldn't parse time: %s", slave_time_gone);
|
|
time_before_missing = parse_time (slave_time_missing, "s");
|
|
if (time_before_missing < 0)
|
|
krb5_errx (context, 1, "couldn't parse time: %s", slave_time_missing);
|
|
|
|
#ifdef SUPPORT_DETACH
|
|
if (detach_from_console) {
|
|
aret = daemon(0, 0);
|
|
if (aret == -1) {
|
|
/* not much to do if detaching fails... */
|
|
krb5_err(context, 1, aret, "failed to daemon(3)ise");
|
|
}
|
|
}
|
|
#endif
|
|
pidfile (NULL);
|
|
krb5_openlog (context, "ipropd-master", &log_facility);
|
|
krb5_set_warn_dest(context, log_facility);
|
|
|
|
ret = krb5_kt_register(context, &hdb_kt_ops);
|
|
if(ret)
|
|
krb5_err(context, 1, ret, "krb5_kt_register");
|
|
|
|
ret = krb5_kt_resolve(context, keytab_str, &keytab);
|
|
if(ret)
|
|
krb5_err(context, 1, ret, "krb5_kt_resolve: %s", keytab_str);
|
|
|
|
memset(&conf, 0, sizeof(conf));
|
|
if(realm) {
|
|
conf.mask |= KADM5_CONFIG_REALM;
|
|
conf.realm = realm;
|
|
}
|
|
ret = kadm5_init_with_skey_ctx (context,
|
|
KADM5_ADMIN_SERVICE,
|
|
NULL,
|
|
KADM5_ADMIN_SERVICE,
|
|
&conf, 0, 0,
|
|
&kadm_handle);
|
|
if (ret)
|
|
krb5_err (context, 1, ret, "kadm5_init_with_password_ctx");
|
|
|
|
server_context = (kadm5_server_context *)kadm_handle;
|
|
|
|
log_fd = open (server_context->log_context.log_file, O_RDONLY, 0);
|
|
if (log_fd < 0)
|
|
krb5_err (context, 1, errno, "open %s",
|
|
server_context->log_context.log_file);
|
|
|
|
signal_fd = make_signal_socket (context);
|
|
listen_fd = make_listen_socket (context, port_str);
|
|
|
|
kadm5_log_get_version_fd (log_fd, ¤t_version);
|
|
|
|
krb5_warnx(context, "ipropd-master started at version: %lu",
|
|
(unsigned long)current_version);
|
|
|
|
while(exit_flag == 0){
|
|
slave *p;
|
|
fd_set readset;
|
|
int max_fd = 0;
|
|
struct timeval to = {30, 0};
|
|
uint32_t vers;
|
|
|
|
#ifndef NO_LIMIT_FD_SETSIZE
|
|
if (signal_fd >= FD_SETSIZE || listen_fd >= FD_SETSIZE)
|
|
krb5_errx (context, 1, "fd too large");
|
|
#endif
|
|
|
|
FD_ZERO(&readset);
|
|
FD_SET(signal_fd, &readset);
|
|
max_fd = max(max_fd, signal_fd);
|
|
FD_SET(listen_fd, &readset);
|
|
max_fd = max(max_fd, listen_fd);
|
|
|
|
for (p = slaves; p != NULL; p = p->next) {
|
|
if (p->flags & SLAVE_F_DEAD)
|
|
continue;
|
|
FD_SET(p->fd, &readset);
|
|
max_fd = max(max_fd, p->fd);
|
|
}
|
|
|
|
ret = select (max_fd + 1,
|
|
&readset, NULL, NULL, &to);
|
|
if (ret < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
else
|
|
krb5_err (context, 1, errno, "select");
|
|
}
|
|
|
|
if (ret == 0) {
|
|
old_version = current_version;
|
|
kadm5_log_get_version_fd (log_fd, ¤t_version);
|
|
|
|
if (current_version > old_version) {
|
|
krb5_warnx(context,
|
|
"Missed a signal, updating slaves %lu to %lu",
|
|
(unsigned long)old_version,
|
|
(unsigned long)current_version);
|
|
for (p = slaves; p != NULL; p = p->next) {
|
|
if (p->flags & SLAVE_F_DEAD)
|
|
continue;
|
|
send_diffs (context, p, log_fd, database, current_version);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret && FD_ISSET(signal_fd, &readset)) {
|
|
#ifndef NO_UNIX_SOCKETS
|
|
struct sockaddr_un peer_addr;
|
|
#else
|
|
struct sockaddr_storage peer_addr;
|
|
#endif
|
|
socklen_t peer_len = sizeof(peer_addr);
|
|
|
|
if(recvfrom(signal_fd, (void *)&vers, sizeof(vers), 0,
|
|
(struct sockaddr *)&peer_addr, &peer_len) < 0) {
|
|
krb5_warn (context, errno, "recvfrom");
|
|
continue;
|
|
}
|
|
--ret;
|
|
assert(ret >= 0);
|
|
old_version = current_version;
|
|
kadm5_log_get_version_fd (log_fd, ¤t_version);
|
|
if (current_version > old_version) {
|
|
krb5_warnx(context,
|
|
"Got a signal, updating slaves %lu to %lu",
|
|
(unsigned long)old_version,
|
|
(unsigned long)current_version);
|
|
for (p = slaves; p != NULL; p = p->next) {
|
|
if (p->flags & SLAVE_F_DEAD)
|
|
continue;
|
|
send_diffs (context, p, log_fd, database, current_version);
|
|
}
|
|
} else {
|
|
krb5_warnx(context,
|
|
"Got a signal, but no update in log version %lu",
|
|
(unsigned long)current_version);
|
|
}
|
|
}
|
|
|
|
for(p = slaves; p != NULL; p = p->next) {
|
|
if (p->flags & SLAVE_F_DEAD)
|
|
continue;
|
|
if (ret && FD_ISSET(p->fd, &readset)) {
|
|
--ret;
|
|
assert(ret >= 0);
|
|
if(process_msg (context, p, log_fd, database, current_version))
|
|
slave_dead(context, p);
|
|
} else if (slave_gone_p (p))
|
|
slave_dead(context, p);
|
|
else if (slave_missing_p (p))
|
|
send_are_you_there (context, p);
|
|
}
|
|
|
|
if (ret && FD_ISSET(listen_fd, &readset)) {
|
|
add_slave (context, keytab, &slaves, listen_fd);
|
|
--ret;
|
|
assert(ret >= 0);
|
|
}
|
|
write_stats(context, slaves, current_version);
|
|
}
|
|
|
|
if(exit_flag == SIGINT || exit_flag == SIGTERM)
|
|
krb5_warnx(context, "%s terminated", getprogname());
|
|
#ifdef SIGXCPU
|
|
else if(exit_flag == SIGXCPU)
|
|
krb5_warnx(context, "%s CPU time limit exceeded", getprogname());
|
|
#endif
|
|
else
|
|
krb5_warnx(context, "%s unexpected exit reason: %ld",
|
|
getprogname(), (long)exit_flag);
|
|
|
|
write_master_down(context);
|
|
|
|
return 0;
|
|
}
|