#include <errno.h>
#include <setjmp.h>
#include <sys/wait.h>
#include <signal.h>
#ifdef USE_PTHREADS
#include <pthread.h>
#endif
#define NEED_SOCKETS
#include "k5-int.h"
#include "com_err.h"
#include "kadm5_defs.h"
#include "adm.h"
#if HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#ifndef SOMAXCONN
#define SOMAXCONN 5
#endif
#ifdef USE_PTHREADS
#define net_slave_type pthread_t *
#ifndef MAX_SLAVES
#define MAX_SLAVES 2*SOMAXCONN
#endif
#else
#define net_slave_type pid_t
#ifndef MAX_SLAVES
#define MAX_SLAVES 2*SOMAXCONN
#endif
#endif
#define NET_SLAVE_FULL_SLEEP 2
typedef struct _net_slave_info {
int sl_inuse;
net_slave_type sl_id;
krb5_context sl_context;
int sl_socket;
struct sockaddr_in sl_local_addr;
struct sockaddr_in sl_remote_addr;
} net_slave_info;
#define net_waiterr_msg "\004child wait failed - cannot reap children"
#if 0
#define net_def_realm_fmt "%s: cannot get default realm (%s).\n"
#endif
#define net_no_mem_fmt "%s: cannot get memory.\n"
#define net_parse_srv_fmt "%s: cannot parse server name %s (%s).\n"
#define net_no_hostname_fmt "%s: cannot get our host name (%s).\n"
#define net_no_hostent_fmt "%s: cannot get our host entry (%s).\n"
#define net_no_servent_fmt "%s: cannot get service entry for %s (%s).\n"
#define net_sockerr_fmt "%s: cannot open network socket (%s).\n"
#define net_soerr_fmt "%s: cannot set socket options (%s).\n"
#define net_binderr_fmt "%s: cannot bind to network address (%s).\n"
#define net_select_fmt "\004select failed"
#define net_cl_disp_fmt "\004client dispatch failed"
#define net_not_ready_fmt "\004select error - no socket to read"
#define net_dispatch_msg "network dispatch"
#ifdef DEBUG
static int net_debug_level = 0;
#endif
static char *net_service_name = (char *) NULL;
static int net_service_princ_init = 0;
static krb5_principal net_service_principal = (krb5_principal) NULL;
#if 0
static int net_server_addr_init = 0;
#endif
static struct sockaddr_in net_server_addr;
static int net_listen_socket = -1;
static int net_max_slaves = 0;
static net_slave_info *net_slave_table = (net_slave_info *) NULL;
#if POSIX_SETJMP
static sigjmp_buf shutdown_jmp;
#else
static jmp_buf shutdown_jmp;
#endif
extern char *programname;
static net_slave_info *
net_find_free_entry()
{
int i, found;
while (1) {
found = 0;
for (i=0; i<net_max_slaves; i++) {
if (!net_slave_table[i].sl_inuse) {
net_slave_table[i].sl_inuse = 1;
found = 1;
break;
}
}
if (found)
break;
sleep(NET_SLAVE_FULL_SLEEP);
}
return(&net_slave_table[i]);
}
static net_slave_info *
net_find_slave(id)
net_slave_type id;
{
int i, found = 0;
for (i=0; i<net_max_slaves; i++) {
if (net_slave_table[i].sl_inuse &&
(net_slave_table[i].sl_id == id)) {
found = 1;
break;
}
}
if (found)
return(&net_slave_table[i]);
else
return((net_slave_info *) NULL);
}
static void
net_free_slave_entry(entp)
net_slave_info *entp;
{
entp->sl_inuse = 0;
}
static krb5_sigtype
net_shutdown(signo)
int signo;
{
int i;
for (i=0; i<net_max_slaves; i++) {
if (net_slave_table[i].sl_inuse){
#ifdef DEBUG
if (net_slave_table[i].sl_id != (net_slave_type) getpid()) {
#endif
#if USE_PTHREADS
pthread_cancel(*net_slave_table[i].sl_id);
#else
kill(net_slave_table[i].sl_id, SIGKILL);
#endif
#ifdef DEBUG
}
#endif
}
}
sleep(5);
#if POSIX_SETJMP
siglongjmp(shutdown_jmp, 1);
#else
longjmp(shutdown_jmp, 1);
#endif
}
#if !USE_PTHREADS
static krb5_sigtype
net_reaper(signo)
int signo;
{
#ifdef WAIT_USES_INT
int child_exit;
#else
union wait child_exit;
#endif
pid_t deadmeat;
net_slave_info *slent;
while (
(
#ifdef HAVE_WAITPID
deadmeat = waitpid((pid_t) -1, &child_exit, WNOHANG)
#else
deadmeat = wait3(&child_exit, WNOHANG, (struct rusage *) NULL)
#endif
) > 0) {
DPRINT(DEBUG_SPROC, net_debug_level,
("| process %d finished with %d\n", deadmeat, child_exit));
slent = net_find_slave(deadmeat);
if (slent) {
net_free_slave_entry(slent);
}
else {
DPRINT(DEBUG_SPROC, net_debug_level,
("| cannot find slave entry for %d\n", deadmeat));
}
}
if ((deadmeat == -1) && (errno != ECHILD))
com_err(programname, errno, net_waiterr_msg);
}
#endif
#if USE_PTHREADS
static krb5_error_code
net_slave_proto(stent)
net_slave_info *stent;
{
krb5_error_code kret;
DPRINT(DEBUG_CALLS, net_debug_level,
("* net_slave_proto()\n"));
DPRINT(DEBUG_SPROC, net_debug_level,
("| thread %d starting\n", stent->sl_id));
kret = proto_serv(stent->sl_context,
(krb5_int32) stent->sl_id,
stent->sl_socket,
&stent->sl_local_addr,
&stent->sl_remote_addr);
DPRINT(DEBUG_SPROC, net_debug_level,
("| thread %d finished with %d\n", stent->sl_id, kret));
DPRINT(DEBUG_CALLS, net_debug_level,
("* net_slave_proto() = %d\n", kret));
net_free_slave_entry(stent);
return(kret);
}
#endif
static krb5_error_code
net_dispatch_client(kcontext, listen_sock, conn_sock, client_addr)
krb5_context kcontext;
int listen_sock;
int conn_sock;
struct sockaddr_in *client_addr;
{
krb5_error_code kret;
net_slave_info *slent;
DPRINT(DEBUG_CALLS, net_debug_level,
("* net_dispatch_client(listen=%d)\n", listen_sock));
kret = 0;
slent = net_find_free_entry();
slent->sl_context = kcontext;
slent->sl_socket = conn_sock;
memcpy((char *) &slent->sl_remote_addr,
(char *) client_addr,
sizeof(struct sockaddr_in));
memcpy((char *) &slent->sl_local_addr,
(char *) &net_server_addr,
sizeof(struct sockaddr_in));
#ifdef DEBUG
if ((net_debug_level & DEBUG_NOSLAVES) == 0) {
#endif
#if USE_PTHREADS
if (!slent->sl_id)
slent->sl_id = (pthread_t *) malloc(sizeof(pthread_t));
if (slent->sl_id == (pthread_t *) NULL) {
kret = ENOMEM;
goto done;
}
if (kret = pthread_create(slent->sl_id,
pthread_attr_default,
(pthread_startroutine_t) net_slave_proto,
(pthread_addr_t) slent)) {
kret = errno;
goto done;
}
if (pthread_detach(slent->sl_id)) {
DPRINT(DEBUG_SPROC, net_debug_level,
("| (%d) child thread %d detach failed (%d)\n",
getpid(), slent->sl_id, errno));
}
DPRINT(DEBUG_SPROC, net_debug_level,
("| (%d) created child thread %d\n",
getpid(), slent->sl_id));
#else
slent->sl_id = fork();
if (slent->sl_id < 0) {
kret = errno;
slent->sl_inuse = 0;
goto done;
}
if (slent->sl_id > 0) {
DPRINT(DEBUG_SPROC, net_debug_level,
("| (%d) created child process %d\n",
getpid(), slent->sl_id));
close(conn_sock);
kret = 0;
goto done;
}
else {
#if POSIX_SIGNALS
struct sigaction s_action;
#endif
#if POSIX_SIGNALS
(void) sigemptyset(&s_action.sa_mask);
s_action.sa_flags = 0;
s_action.sa_handler = SIG_IGN;
(void) sigaction(SIGINT, &s_action, (struct sigaction *) NULL);
(void) sigaction(SIGTERM, &s_action, (struct sigaction *) NULL);
(void) sigaction(SIGHUP, &s_action, (struct sigaction *) NULL);
(void) sigaction(SIGQUIT, &s_action, (struct sigaction *) NULL);
(void) sigaction(SIGPIPE, &s_action, (struct sigaction *) NULL);
s_action.sa_handler = SIG_DFL;
(void) sigaction(SIGCHLD, &s_action, (struct sigaction *) NULL);
#else
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
signal(SIGHUP, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_DFL);
#endif
close(listen_sock);
slent->sl_id = getpid();
DPRINT(DEBUG_SPROC, net_debug_level,
("| process %d starting\n", slent->sl_id));
kret = proto_serv(slent->sl_context,
(krb5_int32) slent->sl_id,
slent->sl_socket,
&slent->sl_local_addr,
&slent->sl_remote_addr);
DPRINT(DEBUG_SPROC, net_debug_level,
("| process %d exiting with %d\n", getpid(), kret));
exit(kret);
}
#endif
#ifdef DEBUG
}
else {
net_slave_info *sl1;
#if POSIX_SIGNALS
struct sigaction s_action;
#endif
#if POSIX_SIGNALS
(void) sigemptyset(&s_action.sa_mask);
s_action.sa_flags = 0;
s_action.sa_handler = SIG_IGN;
(void) sigaction(SIGPIPE, &s_action, (struct sigaction *) NULL);
#else
signal(SIGPIPE, SIG_IGN);
#endif
DPRINT(DEBUG_SPROC, net_debug_level,
("| (%d) not doing child creation\n", getpid()));
slent->sl_id = (net_slave_type) getpid();
kret = proto_serv(slent->sl_context,
(krb5_int32) slent->sl_id,
slent->sl_socket,
&slent->sl_local_addr,
&slent->sl_remote_addr);
sl1 = net_find_slave(slent->sl_id);
if (sl1)
net_free_slave_entry(sl1);
DPRINT(DEBUG_SPROC, net_debug_level,
("| (%d) returned with %d\n", getpid(), kret));
kret = 0;
}
#endif
done:
DPRINT(DEBUG_CALLS, net_debug_level,
("X net_dispatch_client() = %d\n", kret));
return(kret);
}
krb5_error_code
net_init(kcontext, realm, debug_level, port)
krb5_context kcontext;
char * realm;
int debug_level;
krb5_int32 port;
{
krb5_error_code kret;
char our_host_name[MAXHOSTNAMELEN];
struct hostent *our_hostent;
struct servent *our_servent;
#ifdef DEBUG
net_debug_level = debug_level;
#endif
DPRINT(DEBUG_CALLS, net_debug_level, ("* net_init(port=%d)\n", port));
net_slave_table = (net_slave_info *)
malloc((size_t) (MAX_SLAVES * sizeof(net_slave_info)));
net_service_name = (char *) malloc(strlen(realm) +
strlen(KRB5_ADM_SERVICE_INSTANCE) + 2);
if ((net_service_name == (char *) NULL) ||
(net_slave_table == (net_slave_info *) NULL)) {
kret = ENOMEM;
fprintf(stderr, net_no_mem_fmt, programname);
goto done;
}
(void) sprintf(net_service_name, "%s%s%s",
KRB5_ADM_SERVICE_INSTANCE, "/", realm);
memset((char *) net_slave_table, 0,
(size_t) (MAX_SLAVES * sizeof(net_slave_info)));
net_max_slaves = MAX_SLAVES;
DPRINT(DEBUG_HOST, net_debug_level,
("- name of service is %s\n", net_service_name));
kret = krb5_parse_name(kcontext, net_service_name, &net_service_principal);
if (kret) {
fprintf(stderr, net_parse_srv_fmt, programname, net_service_name,
error_message(kret));
goto done;
}
net_service_princ_init = 1;
#ifdef HAVE_NETINET_IN_H
if (gethostname(our_host_name, sizeof(our_host_name))) {
kret = errno;
fprintf(stderr, net_no_hostname_fmt, programname, error_message(kret));
goto done;
}
if (!(our_hostent = gethostbyname(our_host_name))) {
kret = KRB5_ERR_BAD_HOSTNAME;
fprintf(stderr, net_no_hostent_fmt, programname, error_message(kret));
goto done;
}
DPRINT(DEBUG_HOST, net_debug_level,
("- name of host is %s\n", our_hostent->h_name));
net_server_addr.sin_family = AF_INET;
memcpy((char *) &net_server_addr.sin_addr,
(char *) our_hostent->h_addr,
sizeof(net_server_addr.sin_addr));
DPRINT(DEBUG_HOST, net_debug_level,
("- address of host is %x\n",
ntohl(net_server_addr.sin_addr.s_addr)));
if (port > 0) {
net_server_addr.sin_port = htons(port);
DPRINT(DEBUG_HOST, net_debug_level,
("- service name (%s) is on port %d from options\n",
KRB5_ADM_SERVICE_NAME,
ntohs(net_server_addr.sin_port)));
}
else {
char **admin_hostlist;
const char *realm_admin_names[4];
krb5_boolean found;
admin_hostlist = (char **) NULL;
realm_admin_names[0] = "realms";
realm_admin_names[1] = realm;
realm_admin_names[2] = "admin_server";
realm_admin_names[3] = (char *) NULL;
found = 0;
#ifndef OLD_CONFIG_FILES
if (!(kret = profile_get_values(kcontext->profile,
realm_admin_names,
&admin_hostlist))) {
int hi;
char *cport;
char *cp;
krb5_int32 pport;
int ai;
cport = (char *) NULL;
pport = KRB5_ADM_DEFAULT_PORT;
for (hi=0; admin_hostlist[hi]; hi++) {
cp = strchr(admin_hostlist[hi], ' ');
if (cp)
*cp = '\0';
cp = strchr(admin_hostlist[hi], '\t');
if (cp)
*cp = '\0';
cport = strchr(admin_hostlist[hi], ':');
if (cport) {
*cport = '\0';
cport++;
if (sscanf(cport, "%d", &pport) != 1) {
DPRINT(DEBUG_HOST, net_debug_level,
("- profile entry for %s has bad port %s\n",
admin_hostlist[hi],
cport));
pport = KRB5_ADM_DEFAULT_PORT;
}
}
if (!strcmp(admin_hostlist[hi], our_hostent->h_name)) {
net_server_addr.sin_port = ntohs((u_short) pport);
DPRINT(DEBUG_HOST, net_debug_level,
("- service name (%s) is on port %d from profile\n",
KRB5_ADM_SERVICE_NAME,
pport));
found = 1;
}
else {
for (ai=0; our_hostent->h_aliases[ai]; ai++) {
if (!strcmp(admin_hostlist[hi],
our_hostent->h_aliases[ai])) {
net_server_addr.sin_port = ntohs(pport);
DPRINT(DEBUG_HOST, net_debug_level,
("- service name (%s) is on port %d from profile and alias\n",
KRB5_ADM_SERVICE_NAME,
pport));
found = 1;
break;
}
}
}
}
krb5_xfree(admin_hostlist);
}
#endif
if (!found) {
if (!(our_servent = getservbyname(KRB5_ADM_SERVICE_NAME, "tcp"))) {
kret = errno;
fprintf(stderr, net_no_servent_fmt, programname,
KRB5_ADM_SERVICE_NAME, error_message(kret));
goto done;
}
net_server_addr.sin_port = our_servent->s_port;
DPRINT(DEBUG_HOST, net_debug_level,
("- service name (%s) is on port %d from services\n",
our_servent->s_name,
ntohs(our_servent->s_port)));
}
}
#if 0
net_server_addr_init = 1;
#endif
net_listen_socket = socket(AF_INET, SOCK_STREAM, 0);
if (net_listen_socket < 0) {
kret = errno;
fprintf(stderr, net_sockerr_fmt, programname, error_message(kret));
goto done;
}
if (net_server_addr.sin_port != htons(KRB5_ADM_DEFAULT_PORT)) {
int allowed;
allowed = 1;
if (setsockopt(net_listen_socket,
SOL_SOCKET,
SO_REUSEADDR,
(char *) &allowed,
sizeof(allowed)) < 0) {
kret = errno;
fprintf(stderr, net_soerr_fmt, programname, error_message(kret));
goto done;
}
}
if (bind(net_listen_socket,
(struct sockaddr *) &net_server_addr,
sizeof(net_server_addr)) < 0) {
kret = errno;
fprintf(stderr, net_binderr_fmt, programname, error_message(kret));
goto done;
}
else {
DPRINT(DEBUG_HOST, net_debug_level,
("- bound socket %d on port\n", net_listen_socket));
kret = 0;
}
#else
kret = ENOENT;
#endif
done:
DPRINT(DEBUG_CALLS, net_debug_level, ("X net_init() = %d\n", kret));
return(kret);
}
void
net_finish(kcontext, debug_level)
krb5_context kcontext;
int debug_level;
{
DPRINT(DEBUG_CALLS, net_debug_level, ("* net_finish()\n"));
if (net_max_slaves) {
net_max_slaves = 0;
free(net_slave_table);
}
if (net_listen_socket >= 0)
close(net_listen_socket);
if (net_service_princ_init)
krb5_free_principal(kcontext, net_service_principal);
if (net_service_name)
free(net_service_name);
DPRINT(DEBUG_CALLS, net_debug_level, ("X net_finish()\n"));
}
krb5_error_code
net_dispatch(kcontext, detached)
krb5_context kcontext;
int detached;
{
volatile krb5_error_code kret;
fd_set mask, readfds;
int nready;
#if POSIX_SIGNALS
struct sigaction s_action;
#endif
DPRINT(DEBUG_CALLS, net_debug_level, ("* net_dispatch()\n"));
kret = 0;
FD_ZERO(&mask);
FD_SET(net_listen_socket, &mask);
#if POSIX_SIGNALS
(void) sigemptyset(&s_action.sa_mask);
s_action.sa_flags = 0;
s_action.sa_handler = net_shutdown;
(void) sigaction(SIGTERM, &s_action, (struct sigaction *) NULL);
#ifdef DEBUG
(void) sigaction(SIGINT, &s_action, (struct sigaction *) NULL);
#endif
if (!detached)
(void) sigaction(SIGHUP, &s_action, (struct sigaction *) NULL);
#else
signal(SIGTERM, net_shutdown);
#ifdef DEBUG
signal(SIGINT, net_shutdown);
#endif
if (!detached)
signal(SIGHUP, net_shutdown);
#endif
#if !USE_PTHREADS
#if POSIX_SIGNALS
s_action.sa_handler = net_reaper;
(void) sigaction(SIGCHLD, &s_action, (struct sigaction *) NULL);
#else
signal(SIGCHLD, net_reaper);
#endif
#endif
DPRINT(DEBUG_OPERATION, net_debug_level, ("+ listening on socket\n"));
if (
#if POSIX_SETJMP
sigsetjmp(shutdown_jmp, 1) == 0
#else
setjmp(shutdown_jmp) == 0
#endif
) {
if (listen(net_listen_socket, SOMAXCONN) < 0)
kret = errno;
}
else
kret = EINTR;
DPRINT(DEBUG_OPERATION, net_debug_level, ("+ listen done\n"));
while (kret == 0) {
if (
#if POSIX_SETJMP
sigsetjmp(shutdown_jmp, 1) == 0
#else
setjmp(shutdown_jmp) == 0
#endif
) {
readfds = mask;
DPRINT(DEBUG_OPERATION, net_debug_level, ("+ doing select\n"));
if ((nready = select(net_listen_socket+1,
&readfds,
(fd_set *) NULL,
(fd_set *) NULL,
(struct timeval *) NULL)) == 0) {
DPRINT(DEBUG_OPERATION, net_debug_level, ("+ nobody ready\n"));
continue;
}
if ((nready < 0) && (errno != EINTR)) {
com_err(net_dispatch_msg, errno, net_select_fmt);
continue;
}
if (FD_ISSET(net_listen_socket, &readfds)) {
struct sockaddr_in client_addr;
int addrlen;
int conn_sock;
addrlen = sizeof(client_addr);
DPRINT(DEBUG_OPERATION, net_debug_level,
("+ accept connection\n"));
while (((conn_sock = accept(net_listen_socket,
(struct sockaddr *) &client_addr,
&addrlen)) < 0) &&
(errno == EINTR));
if (conn_sock < 0) {
kret = errno;
break;
}
DPRINT(DEBUG_OPERATION, net_debug_level,
("+ accepted connection\n"));
kret = net_dispatch_client(kcontext,
net_listen_socket,
conn_sock,
&client_addr);
if (kret) {
com_err(net_dispatch_msg, kret, net_cl_disp_fmt);
continue;
}
DPRINT(DEBUG_OPERATION, net_debug_level,
("+ dispatch done\n"));
}
else {
com_err(net_dispatch_msg, 0, net_not_ready_fmt);
kret = EIO;
}
}
else {
DPRINT(DEBUG_OPERATION, net_debug_level,
("+ dispatch interrupted by SIGTERM\n"));
kret = 0;
break;
}
}
DPRINT(DEBUG_CALLS, net_debug_level, ("X net_dispatch() = %d\n", kret));
return(kret);
}
krb5_principal
net_server_princ()
{
if (net_service_princ_init)
return(net_service_principal);
else
return((krb5_principal) NULL);
}