kpropd.c revision 159d09a20817016f09b3ea28d1bdada4a336bb91
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* All rights reserved.
*
* Export of this software from the United States of America may require
* a specific license from the United States Government. It is the
* responsibility of any person or organization contemplating export to
* obtain such a license before exporting.
*
* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
* distribute this software and its documentation for any purpose and
* without fee is hereby granted, provided that the above copyright
* notice appear in all copies and that both that copyright notice and
* this permission notice appear in supporting documentation, and that
* the name of FundsXpress. not be used in advertising or publicity pertaining
* to distribution of the software without specific, written prior
* permission. FundsXpress makes no representations about the suitability of
* this software for any purpose. It is provided "as is" without express
* or implied warranty.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
/*
*
* Copyright 1990,1991 by the Massachusetts Institute of Technology.
* All Rights Reserved.
*
* Export of this software from the United States of America may
* require a specific license from the United States Government.
* It is the responsibility of any person or organization contemplating
* export to obtain such a license before exporting.
*
* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
* distribute this software and its documentation for any purpose and
* without fee is hereby granted, provided that the above copyright
* notice appear in all copies and that both that copyright notice and
* this permission notice appear in supporting documentation, and that
* the name of M.I.T. not be used in advertising or publicity pertaining
* to distribution of the software without specific, written prior
* permission. Furthermore if you modify this software you must label
* your software as modified software and not distribute it in such a
* fashion that it might be confused with the original M.I.T. software.
* M.I.T. makes no representations about the suitability of
* this software for any purpose. It is provided "as is" without express
* or implied warranty.
*
*
* XXX We need to modify the protocol so that an acknowledge is set
* after each block, instead after the entire series is sent over.
* The reason for this is so that error packets can get interpreted
* right away. If you don't do this, the sender may never get the
* error packet, because it will die an EPIPE trying to complete the
* write...
*/
#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <netdb.h>
#include <syslog.h>
#include <libintl.h>
#include <locale.h>
#include <k5-int.h>
#include <socket-utils.h>
#include "com_err.h"
#include <errno.h>
#include "kprop.h"
#include <iprop_hdr.h>
#include "iprop.h"
/* Solaris Kerberos */
#include <libgen.h>
#define SYSLOG_CLASS LOG_DAEMON
/*
* This struct simulates the use of _kadm5_server_handle_t
*/
typedef struct _kadm5_iprop_handle_t {
char *cache_name;
int destroy_cache;
struct _kadm5_iprop_handle_t *lhandle;
static char *kprop_version = KPROP_PROT_VERSION;
char *progname;
int debug = 0;
char *srvtab = 0;
int standalone = 0;
char *file = KPROPD_DEFAULT_FILE;
char *temp_file_name;
char *kdb5_util = KPROPD_DEFAULT_KDB5_UTIL;
char *kerb_database = NULL;
char *acl_file_name = KPROPD_ACL_FILE;
short port = 0;
void PRS
(int, char**);
int do_standalone
void doit
(int);
int,
krb5_enctype *,
struct sockaddr_storage);
void recv_database
int,
int,
krb5_data *);
void load_database
char *,
char *);
void send_error
int,
char *);
void recv_error
krb5_data *);
int convert_polltime
(char *);
unsigned int backoff_from_master
(int *);
static void usage()
{
progname);
gettext("\t[-r realm] [-s srvtab] [-dS] [-f slave_file]\n"));
gettext("\t[-F kerberos_db_file ] [-p kdb5_util_pathname]\n"));
exit(1);
}
int
int argc;
char **argv;
{
int ret = 0;
int iprop_supported;
/*
* We wanna do iprop !
*/
if (retval) {
/* Solaris Kerberos: Keep error messages consistent */
gettext("while determining if dbmodule plugin "
"supports iprop"));
exit(1);
}
if (!iprop_supported) {
/* Solaris Kerberos: Keep error messages consistent */
gettext("Current dbmodule plugin does not support "
"iprop"));
exit(1);
}
/*
* Solaris Kerberos:
* Ensure that kpropd is only run on a slave
*/
&is_master)) {
gettext("while trying to determine whether host is "
"master KDC for realm %s"), def_realm);
exit(1);
}
gettext("%s is the master KDC for the realm %s. "
"%s can only be run on a slave KDC"),
exit(1);
}
if (retval) {
/* Solaris Kerberos: Keep error messages consistent */
gettext("while doing iprop"));
exit(1);
}
} else {
/*
* Solaris Kerberos:
* Ensure that the kpropd.acl file exists and contains at least
* 1 entry.
*/
int seen_file = 0;
char buf[1024];
if (!tmp_acl_file) {
gettext("while opening acl file %s"),
exit(1);
}
break;
seen_file = 1;
}
if (!seen_file) {
gettext("No entries found in %s. Can't "
"authorize propagation requests"), acl_file_name);
exit(1);
}
if (standalone)
else
doit(0);
}
}
{
int on = 1;
/* listen for either ipv4 or ipv6 */
if (finet < 0 ) {
exit(1);
}
if(!port) {
exit(1);
}
} else
/*
* We need to close the socket immediately if iprop is enabled,
* since back-to-back full resyncs are possible, so we do not
* linger around for too long
*/
if (iproprole == IPROP_SLAVE) {
gettext("while setting socket option (SO_REUSEADDR)"));
gettext("while setting socket option (SO_LINGER)"));
}
if (debug) {
on = 1;
gettext("%s: attempting to rebind socket "
"with SO_REUSEADDR\n"), progname);
gettext("while setting socket option (SO_REUSEADDR)"));
}
}
if (ret < 0) {
/*
* Solaris Kerberos:
* com_err will print the err msg associated with errno
*/
#if 0
#endif
gettext("while binding listener socket"));
exit(1);
}
}
/* Solaris Kerberos: Indicate where further messages will be sent */
gettext("%s: Logging to SYSLOG with LOG_DAEMON facility\n"),
progname);
if (daemon(1, 0)) {
exit(1);
}
}
#ifdef PID_FILE
} else
gettext("while opening pid file %s for writing"),
PID_FILE);
#endif
/* Solaris Kerberos: Keep error messages consistent */
exit(1);
}
while (1) {
int child_pid;
if (s < 0) {
/* Solaris Kerberos: Keep error messages consistent */
gettext("while accepting connection"));
}
continue;
}
child_pid = 0;
else
switch (child_pid) {
case -1:
exit(1);
/*NOTREACHED*/
case 0:
/* child */
doit(s);
close(s);
_exit(0);
/*NOTREACHED*/
default:
/* parent */
gettext("while waiting to receive database"));
exit(1);
}
close(s);
if (iproprole == IPROP_SLAVE)
return (ret);
}
if (iproprole == IPROP_SLAVE)
break;
}
return (0);
}
int fd;
{
struct sockaddr_storage from;
int on = 1;
int lock_fd;
int database_fd;
if (retval) {
exit(1);
}
exit(1);
}
sizeof (on)) < 0) {
gettext("while attempting setsockopt (SO_KEEPALIVE)"));
}
NULL, 0, NI_NUMERICHOST) != 0) {
/* getnameifo failed so use inet_ntop() to get printable addresses */
sizeof(ntop));
}
/* ipv4 mapped ipv6 addrs handled later */
}
/* coerce ipv4 mapped ipv6 addr to normal ipv4 addr */
v4addr);
}
if (debug)
} else {
/* address family isn't either AF_INET || AF_INET6 */
gettext("Connection from unknown address family:%d"),
if (debug) {
}
}
/*
* Now do the authentication
*/
char *name;
if (retval) {
/* Solaris Kerberos: Keep error messages consistent */
gettext("while unparsing client name"));
exit(1);
}
gettext("Rejected connection from unauthorized principal %s"),
name);
exit(1);
}
if (retval) {
gettext("while trying to lock '%s'"),
exit(1);
}
gettext("while opening database file, '%s'"),
exit(1);
}
/* Solaris Kerberos: Keep error messages consistent */
gettext("while renaming %s to %s"),
exit(1);
}
if (retval) {
gettext("while downgrading lock on '%s'"),
exit(1);
}
if (retval) {
exit(1);
}
/*
* Send the acknowledgement message generated in
* recv_database, then close the socket.
*/
if (retval) {
gettext("while sending # of received bytes"));
exit(1);
}
gettext("while trying to close database file"));
exit(1);
}
exit(0);
}
/*
* Routine to handle incremental update transfer(s) from master KDC
*/
void *server_handle = NULL;
char *iprop_svc_princstr = NULL;
char *master_svc_princstr = NULL;
char *admin_server = NULL;
char *keytab_name = NULL;
unsigned int pollin, backoff_time;
int backoff_cnt = 0;
int reinit_cnt = 0;
int ret;
static kdb_last_t mylast;
char *full_resync_arg = NULL;
char kt_name[MAX_KEYTAB_NAME_LEN];
/*
* Solaris Kerberos:
* Delay daemonizing until some basic configuration checks have been
* performed
*/
#if 0
if (!debug)
daemon(0, 0);
#endif
pollin = (unsigned int)0;
if (master_svc_princstr == NULL) {
def_realm, &master_svc_princstr)) {
/* Solaris Kerberos: keep error messages consistent */
gettext("while getting kiprop host based "
"service name for realm %s"), def_realm);
exit(1);
}
}
/*
* Set cc to the default credentials cache
*/
gettext("while opening default "
"credentials cache"));
exit(1);
}
if (retval) {
"host service principal"));
exit(1);
}
/* Solaris Kerberos */
iprop_svc_principal))) {
("while determining local service principal name"));
exit(1);
}
}
&iprop_svc_princstr)) {
gettext("while canonicalizing "
"principal name"));
exit(1);
}
/*
* Solaris Kerberos:
* Check to see if kiprop/<fqdn>@REALM is in the keytab
*/
kt_name[0] = '\0';
"name of the default keytab"));
}
"keytab"));
exit(1);
}
0, 0, &entry)) {
"from %s"), iprop_svc_princstr,
exit(1);
}
if (!debug) {
/* Solaris Kerberos: Indicate where further messages will be sent */
if (daemon(0, 0)) {
exit(1);
}
}
/*
* Authentication, initialize rpcsec_gss handle etc.
*/
¶ms,
NULL,
if (retval) {
if (retval == KADM5_RPC_ERROR) {
reinit_cnt++;
if (server_handle)
kadm5_destroy((void *) server_handle);
server_handle = (void *)NULL;
"while attempting to connect"
" to master KDC ... retrying"));
(void) sleep(backoff_time);
goto reinit;
} else {
/* Solaris Kerberos: Be more verbose */
gettext("while initializing %s interface for "
if (retval == KADM5_BAD_CLIENT_PARAMS ||
usage();
exit(1);
}
}
/*
* Reset re-initialization count to zero now.
*/
reinit_cnt = backoff_time = 0;
/*
* Reset the handle to the correct type for the RPC call
*/
/*
* If we have reached this far, we have succesfully established
* a RPCSEC_GSS connection; we now start polling for updates
*/
/* Solaris Kerberos: Keep error messages consistent */
gettext("while allocating poll_time"));
exit(1);
}
}
if (pollin == (unsigned int)0)
for (;;) {
/*
* Get the most recent ulog entry sno + ts, which
* we package in the request to the master KDC
*/
/*
* Loop continuously on an iprop_get_updates_1(),
* so that we can keep probing the master for updates
* or (if needed) do a full resync of the krb5 db.
*/
"iprop_get_updates call failed");
if (server_handle)
kadm5_destroy((void *)server_handle);
server_handle = (void *)NULL;
goto reinit;
}
/*
* We dont do a full resync again, if the last
* X'fer was a resync and if the master sno is
* still "0", i.e. no updates so far.
*/
== 0)) {
break;
} else {
full_ret = iprop_full_resync_1((void *)
if (full_ret == (kdb_fullresync_result_t *)
NULL) {
"iprop_full_resync call failed");
if (server_handle)
kadm5_destroy((void *)
server_handle = (void *)NULL;
goto reinit;
}
}
case UPDATE_OK:
backoff_cnt = 0;
/*
* We now listen on the kprop port for
* the full dump
*/
if (ret)
gettext("kpropd: Full resync, "
"invalid return."));
if (debug)
if (ret)
gettext("Full resync "
"was unsuccessful\n"));
else
gettext("Full resync "
"was successful\n"));
break;
case UPDATE_BUSY:
/*
* Exponential backoff
*/
backoff_cnt++;
break;
case UPDATE_NIL:
default:
backoff_cnt = 0;
" invalid return from master KDC."));
break;
case UPDATE_PERM_DENIED:
" permission denied."));
goto error;
case UPDATE_ERROR:
" error returned from master KDC."));
goto error;
}
break;
case UPDATE_OK:
backoff_cnt = 0;
/*
* ulog_replay() will convert the ulog updates to db
* entries using the kdb conv api and will commit
* the entries to the slave kdc database
*/
if (retval) {
" failed, updates not registered."));
break;
}
if (debug)
"from master was OK\n"));
break;
case UPDATE_PERM_DENIED:
" permission denied."));
goto error;
case UPDATE_ERROR:
"returned from master KDC."));
goto error;
case UPDATE_BUSY:
/*
* Exponential backoff
*/
backoff_cnt++;
break;
case UPDATE_NIL:
/*
* Master-slave are in sync
*/
if (debug)
"are in-sync, no updates\n"));
backoff_cnt = 0;
break;
default:
backoff_cnt = 0;
" invalid return from master KDC."));
break;
}
goto done;
/*
* Sleep for the specified poll interval (Default is 2 mts),
* or do a binary exponential backoff if we get an
* UPDATE_BUSY signal
*/
if (backoff_cnt > 0) {
if (debug)
"from master, backoff for %d secs\n"),
(void) sleep(backoff_time);
}
else
}
if (debug)
" bailing.\n"));
done:
if (poll_time)
if (master_svc_princstr)
gettext("while closing default ccache"));
exit(1);
}
if (def_realm)
if (server_handle)
kadm5_destroy((void *)server_handle);
if (kpropd_context)
return (0);
else
exit(1);
}
/*
* Do exponential backoff, since master KDC is BUSY or down
*/
unsigned int backoff_from_master(int *cnt) {
unsigned int btime;
if (btime > MAX_BACKOFF) {
btime = MAX_BACKOFF;
*cnt--;
}
return (btime);
}
/*
* Routine to convert the `pollstr' string to seconds
*/
int convert_polltime(char *pollstr) {
}
}
}
/*
* If we have a bogus pollstr value, set polltime to the
* default of 2 mts (120 seconds).
*/
if (polltime == 0)
polltime = 120;
return (polltime);
}
static void
const char *whoami;
long code;
const char *fmt;
{
char error_buf[8096];
error_buf[0] = '\0';
if (fmt)
}
int argc;
char **argv;
{
char *cp;
int c;
static const char tmp[] = ".temp";
#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
#endif
(void) textdomain(TEXT_DOMAIN);
if (retval) {
gettext("while initializing krb5"));
exit(1);
}
/* Solaris Kerberos: Sanitize progname */
switch (c) {
case 'd':
debug++;
break;
case 't':
/*
* Undocumented option - for testing only.
*
* Option to run the kpropd server exactly
* once (this is true only if iprop is enabled).
*/
break;
case 'f':
if (!file)
usage();
break;
case 'F':
if (!kerb_database)
usage();
break;
case 'p':
if (!kdb5_util)
usage();
break;
case 'P':
if (!port)
usage();
break;
case 'r':
if (!realm)
usage();
break;
case 's':
if (!srvtab)
usage();
break;
case 'S':
standalone++;
break;
case 'a':
if (!acl_file_name)
usage();
break;
case '?':
default:
usage();
}
}
/*
* If not in debug mode, switch com_err reporting to syslog
*/
if (! debug) {
/*
* Solaris Kerberos:
* Don't replace default logging. Add a new logging channel.
* Stop logging to stderr when daemonizing
*/
}
/*
* Get my hostname, so we can construct my service name
*/
if (retval) {
/* Solaris Kerberos: Keep error messages consistent */
gettext("while trying to construct my service name"));
exit(1);
}
if (realm) {
if (retval) {
gettext("while constructing my service realm"));
exit(1);
}
}
/*
* Construct the name of the temporary file.
*/
gettext("while allocating filename for temp file"));
exit(1);
}
¶ms);
if (retval) {
exit(1);
}
/* Solaris Kerberos: Keep error messages consistent */
gettext("while mapping log"));
exit(1);
}
}
/*
* Grab the realm info and check if iprop is enabled.
*/
if (retval) {
/* Solaris Kerberos: Keep error messages consistent */
gettext("while retrieving default realm"));
exit(1);
}
}
}
/*
* Figure out who's calling on the other end of the connection....
*/
void
int fd;
struct sockaddr_storage ss;
{
struct sockaddr_storage r_ss;
int ss_length;
/*
* Set recv_addr and send_addr
*/
gettext("while converting socket address"));
exit(1);
}
gettext("while getting local socket address"));
exit(1);
}
gettext("while converting socket address"));
exit(1);
}
if (debug) {
char *name;
if (retval) {
/* Solaris Kerberos: Keep error messages consistent */
exit(1);
}
name);
}
if (retval) {
exit(1);
}
if (retval) {
exit(1);
}
&sender_addr);
if (retval) {
exit(1);
}
if (srvtab) {
if (retval) {
exit(1);
}
}
if (retval) {
exit(1);
}
if (retval) {
exit(1);
}
if (debug) {
char * name;
char etypebuf[100];
if (retval) {
/* Solaris Kerberos: Keep error messages consistent */
gettext("while unparsing client name"));
exit(1);
}
if (retval) {
/* Solaris Kerberos: Keep error messages consistent */
exit(1);
}
}
}
{
char buf[1024];
int end;
if (retval)
return FALSE;
if (!acl_file)
return FALSE;
break;
/* if the next character is not whitespace or nul, then
the match is only partial. continue on to new lines. */
continue;
/* otherwise, skip trailing whitespace */
/* now, look for an etype string. if there isn't one,
return true. if there is an invalid string, continue.
If there is a valid string, return true only if it
matches the etype passed in, otherwise continue */
if ((*ptr) &&
(acl_etype != auth_etype)))
continue;
return TRUE;
}
}
return FALSE;
}
void
int fd;
int database_fd;
{
int received_size, n;
char buf[1024];
/*
* Receive and decode size from client
*/
if (retval) {
gettext("while reading size of database from client"));
exit(1);
}
if (krb5_is_krb_error(&inbuf))
if (retval) {
"while decoding database size"));
gettext("while decoding database size from client"));
exit(1);
}
/*
* Initialize the initial vector.
*/
if (retval) {
"failed while initializing i_vector"));
exit(1);
}
/*
* Now start receiving the database from the net
*/
received_size = 0;
while (received_size < database_size) {
if (retval) {
gettext("while reading database block starting at offset %d"),
exit(1);
}
if (krb5_is_krb_error(&inbuf))
if (retval) {
gettext("while decoding database block starting at offset %d"),
exit(1);
}
if (n < 0) {
"while writing database block starting at offset %d"),
"incomplete write while writing database block starting at\n"
"offset %d (%d written, %d expected)"),
}
/* SUNWresync121: our krb5...contents sets length to 0 */
}
/*
* OK, we've seen the entire file. Did we get too many bytes?
*/
if (received_size > database_size) {
gettext("Received %d bytes, expected %d bytes for database file"),
}
/*
* Create message acknowledging number of bytes received, but
* don't send it until kdb5_util returns successfully.
*/
if (retval) {
gettext("while encoding # of receieved bytes"));
gettext("while encoding # of received bytes"));
exit(1);
}
}
void
int fd;
char *err_text;
{
const char *text;
char buf[1024];
if (err_text)
else
if (err_text) {
err_text);
}
}
}
}
}
void
{
if (retval) {
gettext("while decoding error packet from client"));
exit(1);
}
gettext("Generic remote error: %s\n"),
gettext("signalled from server"));
gettext("Error text from client: %s\n"),
}
exit(1);
}
void
char *kdb_util;
char *database_file_name;
{
static char *edit_av[10];
int child_pid;
int count;
BSD systems */
#ifndef WEXITSTATUS
#define WEXITSTATUS(w) (w).w_retcode
#endif
#else
int waitb;
#endif
if (debug)
count = 1;
if (realm) {
}
if (kerb_database) {
}
}
case -1:
kdb_util);
exit(1);
/*NOTREACHED*/
case 0:
if (!debug) {
close(0);
close(1);
close(2);
dup(0);
dup(0);
}
if (!debug)
kdb_util);
_exit(1);
/*NOTREACHED*/
default:
if (debug)
kdb_util);
exit(1);
}
}
if (error_ret) {
gettext("%s returned a bad exit status (%d)"),
exit(1);
}
return;
}