/* * kadmin/v5server/srv_net.c * * Copyright 1995 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. 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. * */ /* * srv_net.c - handle networking functions of the administrative server. */ #include #include #include #include #ifdef USE_PTHREADS #include #endif /* USE_PTHREADS */ #define NEED_SOCKETS #include "k5-int.h" #include "com_err.h" #include "kadm5_defs.h" #include "adm.h" #if HAVE_SYS_SELECT_H #include #endif /* linux doesn't have SOMAXCONN */ #ifndef SOMAXCONN #define SOMAXCONN 5 #endif /* * This module can use the pthreads library. To do so, define USE_PTHREADS. * You'll need to find out what else pthreads requires (e.g. -lmach -lc_r * under OSF/1). */ #ifdef USE_PTHREADS #define net_slave_type pthread_t * #ifndef MAX_SLAVES #define MAX_SLAVES 2*SOMAXCONN #endif /* MAX_SLAVES */ #else /* USE_PTHREADS */ #define net_slave_type pid_t #ifndef MAX_SLAVES #define MAX_SLAVES 2*SOMAXCONN #endif /* MAX_SLAVES */ #endif /* USE_PTHREADS */ #define NET_SLAVE_FULL_SLEEP 2 /* seconds */ /* * Slave information storage. */ 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; /* local address */ struct sockaddr_in sl_remote_addr; /* remote address */ } net_slave_info; /* * Error messages. */ static const char *net_waiterr_msg = "\004child wait failed - cannot reap children"; static const char *net_def_realm_fmt = "%s: cannot get default realm (%s).\n"; static const char *net_no_mem_fmt = "%s: cannot get memory.\n"; static const char *net_parse_srv_fmt = "%s: cannot parse server name %s (%s).\n"; static const char *net_no_hostname_fmt = "%s: cannot get our host name (%s).\n"; static const char *net_no_hostent_fmt = "%s: cannot get our host entry (%s).\n"; static const char *net_no_servent_fmt = "%s: cannot get service entry for %s (%s).\n"; static const char *net_sockerr_fmt = "%s: cannot open network socket (%s).\n"; static const char *net_soerr_fmt = "%s: cannot set socket options (%s).\n"; static const char *net_binderr_fmt = "%s: cannot bind to network address (%s).\n"; static const char *net_select_fmt = "\004select failed"; static const char *net_cl_disp_fmt = "\004client dispatch failed"; static const char *net_not_ready_fmt = "\004select error - no socket to read"; static const char *net_dispatch_msg = "network dispatch"; static int net_debug_level = 0; static char *net_service_name = (char *) NULL; static int net_service_princ_init = 0; static krb5_principal net_service_principal = (krb5_principal) NULL; static int net_server_addr_init = 0; static struct sockaddr_in net_server_addr; static int net_listen_socket = -1; static int net_slaves_active = 0; 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 /* POSIX_SETJMP */ static jmp_buf shutdown_jmp; #endif /* POSIX_SETJMP */ extern char *programname; /* * net_find_free_entry() - Find a free entry in the slave table. */ static net_slave_info * net_find_free_entry() { int i, found; /* Find a table entry */ while (1) { found = 0; for (i=0; isl_inuse = 0; } /* * net_shutdown() - Destroy all slaves on signal reception */ static krb5_sigtype net_shutdown(signo) int signo; { int i; /* Loop through all slaves */ for (i=0; i 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 /* USE_PTHREADS */ #if USE_PTHREADS /* * net_slave_proto() - pthread main routine. */ 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 /* USE_PTHREADS */ /* * net_dispatch_client() - Handle client dispatch. */ 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; /* Find a free entry */ slent = net_find_free_entry(); /* Initialize the slave 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 /* DEBUG */ /* Do a real slave creation */ #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 /* USE_PTHREADS */ slent->sl_id = fork(); if (slent->sl_id < 0) { kret = errno; slent->sl_inuse = 0; goto done; } if (slent->sl_id > 0) { /* parent */ 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 /* POSIX_SIGNALS */ /* child */ #if POSIX_SIGNALS (void) sigemptyset(&s_action.sa_mask); s_action.sa_flags = 0; /* Ignore SIGINT, SIGTERM, SIGHUP, SIGQUIT and SIGPIPE */ 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); /* Restore to default SIGCHLD */ s_action.sa_handler = SIG_DFL; (void) sigaction(SIGCHLD, &s_action, (struct sigaction *) NULL); #else /* POSIX_SIGNALS */ signal(SIGINT, SIG_IGN); /* Ignore SIGINT */ signal(SIGTERM, SIG_IGN); /* Ignore SIGTERM */ signal(SIGHUP, SIG_IGN); /* Ignore SIGHUP */ signal(SIGQUIT, SIG_IGN); /* Ignore SIGQUIT */ signal(SIGPIPE, SIG_IGN); /* Ignore SIGPIPE */ signal(SIGCHLD, SIG_DFL); /* restore SIGCHLD handling */ #endif /* POSIX_SIGNALS */ 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 /* USE_PTHREADS */ #ifdef DEBUG } else { net_slave_info *sl1; #if POSIX_SIGNALS struct sigaction s_action; #endif /* POSIX_SIGNALS */ /* * Ignore SIGPIPE. */ #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 /* POSIX_SIGNALS */ signal(SIGPIPE, SIG_IGN); /* Ignore SIGPIPE */ #endif /* POSIX_SIGNALS */ 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 /* DEBUG */ done: DPRINT(DEBUG_CALLS, net_debug_level, ("X net_dispatch_client() = %d\n", kret)); return(kret); } /* * net_init() - Initialize network context. */ 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; net_debug_level = debug_level; DPRINT(DEBUG_CALLS, net_debug_level, ("* net_init(port=%d)\n", port)); /* Allocate the slave table */ net_slave_table = (net_slave_info *) malloc((size_t) (MAX_SLAVES * sizeof(net_slave_info))); /* Make our service name */ 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)); /* Now formulate the principal name */ if (kret = krb5_parse_name(kcontext, net_service_name, &net_service_principal)) { 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 /* Now get our host name/entry */ 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; /* perhaps put h_errno in the msg */ 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)); /* Now initialize our network address */ 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))); /* * Fill in the port address. * If the port is supplied by the invoker, then use that one. * If not, then try the profile, and if all fails, then use the service * entry. */ 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]; /* XXX */ krb5_boolean found; /* * Try to get the service entry out of the profile. */ 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++) { /* * This knows a little too much about the format of profile * entries. Shouldn't it just be some sort of tuple? * * The form is assumed to be: * admin_server = [:[]] */ 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; } } /* * We've stripped away the crud. Now check to see if the * profile entry matches our hostname. If so, then this * is the one to use. Additionally, check the host alias * list. */ 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 /* OLD_CONFIG_FILES */ /* * If we didn't find an entry in the profile, then as a last gasp * effort, attempt to find it in /etc/services. */ if (!found) { /* Get the service entry out of /etc/services */ 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))); } } net_server_addr_init = 1; /* Now open the listen socket */ 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 we have a non-default port number, then allow reuse of address */ 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; } } /* Bind socket */ 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 /* HAVE_NETINET_IN_H */ /* Don't know how to do anything else. */ kret = ENOENT; #endif /* HAVE_NETINET_IN_H */ done: DPRINT(DEBUG_CALLS, net_debug_level, ("X net_init() = %d\n", kret)); return(kret); } /* * net_finish() - Finish network context. */ 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")); } /* * net_dispatch() - Listen and dispatch request. * * Loop forever selecting on the listen socket. When an incoming connection * comes in, dispatch to net_client_connect(). */ krb5_error_code net_dispatch(kcontext, detached) krb5_context kcontext; int detached; { krb5_error_code kret; fd_set mask, readfds; int nready; #if POSIX_SIGNALS struct sigaction s_action; #endif /* POSIX_SIGNALS */ DPRINT(DEBUG_CALLS, net_debug_level, ("* net_dispatch()\n")); kret = 0; /* Set up the fdset mask */ 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 /* DEBUG */ if (!detached) (void) sigaction(SIGHUP, &s_action, (struct sigaction *) NULL); #else /* POSIX_SIGNALS */ /* * SIGTERM (or SIGINT, if debug, or SIGHUP if not detached) shuts us down. */ signal(SIGTERM, net_shutdown); #ifdef DEBUG signal(SIGINT, net_shutdown); #endif /* DEBUG */ if (!detached) signal(SIGHUP, net_shutdown); #endif /* POSIX_SIGNALS */ #if !USE_PTHREADS #if POSIX_SIGNALS s_action.sa_handler = net_reaper; (void) sigaction(SIGCHLD, &s_action, (struct sigaction *) NULL); #else /* POSIX_SIGNALS */ /* * SIGCHILD indicates end of child process life. */ signal(SIGCHLD, net_reaper); #endif /* POSIX_SIGNALS */ #endif /* !USE_PTHREADS */ /* Receive connections on the socket */ DPRINT(DEBUG_OPERATION, net_debug_level, ("+ listening on socket\n")); if ( #if POSIX_SETJMP sigsetjmp(shutdown_jmp, 1) == 0 #else /* POSIX_SETJMP */ setjmp(shutdown_jmp) == 0 #endif /* POSIX_SETJMP */ ) { if (listen(net_listen_socket, SOMAXCONN) < 0) kret = errno; } else kret = EINTR; DPRINT(DEBUG_OPERATION, net_debug_level, ("+ listen done\n")); while (kret == 0) { /* * Prepare to catch signals. */ if ( #if POSIX_SETJMP sigsetjmp(shutdown_jmp, 1) == 0 #else /* POSIX_SETJMP */ setjmp(shutdown_jmp) == 0 #endif /* POSIX_SETJMP */ ) { 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; /* Nobody ready */ } 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); } /* * Return our service principal. */ krb5_principal net_server_princ() { if (net_service_princ_init) return(net_service_principal); else return((krb5_principal) NULL); }