source: web/old/remctl-2.14/server/remctld.c @ f6f3e91

web
Last change on this file since f6f3e91 was f6f3e91, checked in by Jessica B. Hamrick <jhamrick@…>, 15 years ago

Preserve directory hierarchy (not sure what happened to it)

  • Property mode set to 100644
File size: 14.4 KB
Line 
1/*
2 * The remctl server.
3 *
4 * Handles option parsing, network setup, and the basic processing loop of the
5 * remctld server.  Supports either being run from inetd or tcpserver or
6 * running as a stand-alone daemon and managing its own network connections.
7 *
8 * Written by Anton Ushakov
9 * Extensive modifications by Russ Allbery <rra@stanford.edu>
10 * Copyright 2002, 2003, 2004, 2005, 2006, 2007, 2008
11 *     Board of Trustees, Leland Stanford Jr. University
12 *
13 * See LICENSE for licensing terms.
14 */
15
16#include <config.h>
17#include <portable/system.h>
18#include <portable/gssapi.h>
19#include <portable/socket.h>
20
21#include <signal.h>
22#include <syslog.h>
23#include <sys/wait.h>
24#include <time.h>
25
26#include <server/internal.h>
27#include <util/util.h>
28
29/*
30 * Flag indicating whether we've received a SIGCHLD and need to reap children
31 * (only used in standalone mode).
32 */
33static volatile sig_atomic_t child_signaled = 0;
34
35/*
36 * Flag indicating whether we've received a signal asking us to re-read our
37 * configuration file (only used in standalone mode).
38 */
39static volatile sig_atomic_t config_signaled = 0;
40
41/*
42 * Flag indicating whether we've received a signal asking us to exit (only
43 * used in standalone mode).
44 */
45static volatile sig_atomic_t exit_signaled = 0;
46
47/* Usage message. */
48static const char usage_message[] = "\
49Usage: remctld <options>\n\
50\n\
51Options:\n\
52    -d            Log verbose debugging information\n\
53    -f <file>     Config file (default: " CONFIG_FILE ")\n\
54    -h            Display this help\n\
55    -m            Stand-alone daemon mode, meant mostly for testing\n\
56    -P <file>     Write PID to file, only useful with -m\n\
57    -p <port>     Port to use, only for standalone mode (default: 4373)\n\
58    -S            Log to standard output/error rather than syslog\n\
59    -s <service>  Service principal to use (default: host/<host>)\n\
60    -v            Display the version of remctld\n";
61
62/* Structure used to store program options. */
63struct options {
64    bool foreground;
65    bool standalone;
66    bool log_stdout;
67    bool debug;
68    unsigned short port;
69    char *service;
70    const char *config_path;
71    const char *pid_path;
72};
73
74
75/*
76 * Display the usage message for remctld.
77 */
78static void
79usage(int status)
80{
81    fprintf((status == 0) ? stdout : stderr, usage_message);
82    if (status == 0)
83        exit(0);
84    else
85        die("invalid usage");
86}
87
88
89/*
90 * Signal handler for child processes forked when running in standalone mode.
91 * Just set the child_signaled global so that we know to reap the processes
92 * later.
93 */
94static RETSIGTYPE
95child_handler(int sig UNUSED)
96{
97    child_signaled = 1;
98}
99
100
101/*
102 * Signal handler for signals asking us to re-read our configuration file when
103 * running in standalone mode.  Set the config_signaled global so that we do
104 * this the next time through the processing loop.
105 */
106static RETSIGTYPE
107config_handler(int sig UNUSED)
108{
109    config_signaled = 1;
110}
111
112
113/*
114 * Signal handler for signals asking for a clean shutdown when running in
115 * standalone mode.  Set the exit_signaled global so that we exit cleanly the
116 * next time through the processing loop.
117 */
118static RETSIGTYPE
119exit_handler(int sig UNUSED)
120{
121    exit_signaled = 1;
122}
123
124
125/*
126 * Given a service name, imports it and acquires credentials for it, storing
127 * them in the second argument.  Returns true on success and false on failure,
128 * logging an error message.
129 *
130 * Normally, you don't want to do this; instead, normally you want to allow
131 * the underlying GSS-API library choose the appropriate credentials from a
132 * keytab for each incoming connection.
133 */
134static bool
135acquire_creds(char *service, gss_cred_id_t *creds)
136{
137    gss_buffer_desc buffer;
138    gss_name_t name;
139    OM_uint32 major, minor;
140
141    buffer.value = service;
142    buffer.length = strlen(buffer.value) + 1;
143    major = gss_import_name(&minor, &buffer, GSS_C_NT_USER_NAME, &name);
144    if (major != GSS_S_COMPLETE) {
145        warn_gssapi("while importing name", major, minor);
146        return false;
147    }
148    major = gss_acquire_cred(&minor, name, 0, GSS_C_NULL_OID_SET,
149                             GSS_C_ACCEPT, creds, NULL, NULL);
150    if (major != GSS_S_COMPLETE) {
151        warn_gssapi("while acquiring credentials", major, minor);
152        return false;
153    }
154    gss_release_name(&minor, &name);
155    return true;
156}
157
158
159/*
160 * Handle the interaction with the client.  Takes the client file descriptor,
161 * the server configuration, and the server credentials.  Establishes a
162 * security context, processes requests from the client, checks the ACL file
163 * as appropriate, and then spawns commands, sending the output back to the
164 * client.  This function only returns when the client connection has
165 * completed, either successfully or unsuccessfully.
166 */
167static void
168server_handle_connection(int fd, struct config *config, gss_cred_id_t creds)
169{
170    struct client *client;
171
172    /* Establish a context with the client. */
173    client = server_new_client(fd, creds);
174    if (client == NULL) {
175        close(fd);
176        return;
177    }
178    debug("accepted connection from %s (protocol %d)", client->user,
179          client->protocol);
180
181    /*
182     * Now, we process incoming commands.  This is handled differently
183     * depending on the protocol version.  These functions won't exit until
184     * the client is done sending commands and we're done replying.
185     */
186    if (client->protocol == 1)
187        server_v1_handle_commands(client, config);
188    else
189        server_v2_handle_commands(client, config);
190
191    /* We're done; shut down the client connection. */
192    server_free_client(client);
193}
194
195
196/*
197 * Gather information about an exited child and log an appropriate message.
198 * We keep the log level to debug unless something interesting happened, like
199 * a non-zero exit status.
200 */
201static void
202server_log_child(pid_t pid, int status)
203{
204    if (WIFEXITED(status)) {
205        if (WEXITSTATUS(status) != 0)
206            warn("child %lu exited with %d", (unsigned long) pid,
207                 WEXITSTATUS(status));
208        else
209            debug("child %lu done", (unsigned long) pid);
210    } else if (WIFSIGNALED(status)) {
211        warn("child %lu died on signal %d", (unsigned long) pid,
212             WTERMSIG(status));
213    } else {
214        warn("child %lu died", (unsigned long) pid);
215    }
216}
217
218
219/*
220 * Run as a daemon.  This is the main dispatch loop, which listens for network
221 * connections, forks a child to process each connection, and reaps the
222 * children when they're done.  This is only used in standalone mode; when run
223 * from inetd or tcpserver, remctld processes one connection and then exits.
224 */
225static void
226server_daemon(struct options *options, struct config *config,
227              gss_cred_id_t creds)
228{
229    int s, stmp, status;
230    pid_t child;
231    struct sigaction sa, oldsa;
232    struct sockaddr_storage ss;
233    socklen_t sslen;
234    char ip[INET6_ADDRSTRLEN];
235
236    /* We're running as a daemon, so don't self-destruct. */
237    alarm(0);
238
239    /* Set up a SIGCHLD handler so that we know when to reap children. */
240    memset(&sa, 0, sizeof(sa));
241    sa.sa_handler = child_handler;
242    if (sigaction(SIGCHLD, &sa, &oldsa) < 0)
243        sysdie("cannot set SIGCHLD handler");
244
245    /* Set up exit handlers for signals that call for a clean shutdown. */
246    sa.sa_handler = exit_handler;
247    if (sigaction(SIGINT, &sa, NULL) < 0)
248        sysdie("cannot set SIGINT handler");
249    if (sigaction(SIGTERM, &sa, NULL) < 0)
250        sysdie("cannot set SIGTERM handler");
251
252    /* Set up a SIGHUP handler so that we know when to re-read our config. */
253    sa.sa_handler = config_handler;
254    if (sigaction(SIGHUP, &sa, NULL) < 0)
255        sysdie("cannot set SIGHUP handler");
256
257    /* Log a starting message. */
258    notice("starting");
259
260    /* Bind to the network socket. */
261    stmp = network_bind_ipv4("any", options->port);
262    if (stmp < 0)
263        sysdie("cannot create socket");
264    if (listen(stmp, 5) < 0)
265        sysdie("error listening on socket");
266
267    /*
268     * The main processing loop.  Each time through the loop, check to see if
269     * we need to reap children, check to see if we should re-read our
270     * configuration, and check to see if we're exiting.  Then see if we have
271     * a new connection, and if so, fork a child to handle it.
272     *
273     * Note that there are no limits here on the number of simultaneous
274     * processes, so you may want to set system resource limits to prevent an
275     * attacker from consuming all available processes.
276     */
277    do {
278        if (child_signaled) {
279            child_signaled = 0;
280            while ((child = waitpid(0, &status, WNOHANG)) > 0)
281                server_log_child(child, status);
282            if (child < 0 && errno != ECHILD)
283                sysdie("waitpid failed");
284        }
285        if (config_signaled) {
286            config_signaled = 0;
287            notice("re-reading configuration");
288            server_config_free(config);
289            config = server_config_load(options->config_path);
290            if (config == NULL)
291                die("cannot load configuration file %s", options->config_path);
292        }
293        if (exit_signaled) {
294            notice("signal received, exiting");
295            if (options->pid_path != NULL)
296                unlink(options->pid_path);
297            exit(0);
298        }
299        sslen = sizeof(ss);
300        s = accept(stmp, (struct sockaddr *) &ss, &sslen);
301        if (s < 0) {
302            if (errno != EINTR)
303                syswarn("error accepting connection");
304            continue;
305        }
306        fdflag_close_exec(s, true);
307        child = fork();
308        if (child < 0) {
309            syswarn("forking a new child failed");
310            warn("sleeping ten seconds in the hope we recover...");
311            sleep(10);
312        } else if (child == 0) {
313            close(stmp);
314            if (sigaction(SIGCHLD, &oldsa, NULL) < 0)
315                syswarn("cannot reset SIGCHLD handler");
316            server_handle_connection(s, config, creds);
317            if (options->log_stdout)
318                fflush(stdout);
319            exit(0);
320        } else {
321            close(s);
322            network_sockaddr_sprint(ip, sizeof(ip), (struct sockaddr *) &ss);
323            debug("child %lu for %s", (unsigned long) child, ip);
324        }
325    } while (1);
326}
327
328
329/*
330 * Main routine.  Parses command-line arguments, determines whether we're
331 * running in stand-alone or inetd mode, and does the connection handling if
332 * running in standalone mode.  User connections are handed off to
333 * process_connection.
334 */
335int
336main(int argc, char *argv[])
337{
338    struct options options;
339    FILE *pid_file;
340    int option;
341    struct sigaction sa;
342    gss_cred_id_t creds = GSS_C_NO_CREDENTIAL;
343    OM_uint32 minor;
344    struct config *config;
345
346    /*
347     * Since we are normally called from tcpserver or inetd, prevent clients
348     * from holding on to us forever by dying after an hour.
349     */
350    alarm(60 * 60);
351
352    /* Ignore SIGPIPE errors from our children. */
353    memset(&sa, 0, sizeof(sa));
354    sa.sa_handler = SIG_IGN;
355    if (sigaction(SIGPIPE, &sa, NULL) < 0)
356        sysdie("cannot set SIGPIPE handler");
357
358    /* Establish identity. */
359    message_program_name = "remctld";
360
361    /* Initialize options. */
362    memset(&options, 0, sizeof(options));
363    options.port = 4373;
364    options.service = NULL;
365    options.pid_path = NULL;
366    options.config_path = CONFIG_FILE;
367
368    /* Parse options. */
369    while ((option = getopt(argc, argv, "dFf:hk:mP:p:Ss:v")) != EOF) {
370        switch (option) {
371        case 'd':
372            options.debug = true;
373            break;
374        case 'F':
375            options.foreground = true;
376            break;
377        case 'f':
378            options.config_path = optarg;
379            break;
380        case 'h':
381            usage(0);
382            break;
383        case 'k':
384            if (setenv("KRB5_KTNAME", optarg, 1) < 0)
385                sysdie("cannot set KRB5_KTNAME");
386            break;
387        case 'm':
388            options.standalone = true;
389            break;
390        case 'P':
391            options.pid_path = optarg;
392            break;
393        case 'p':
394            options.port = atoi(optarg);
395            break;
396        case 'S':
397            options.log_stdout = true;
398            break;
399        case 's':
400            options.service = optarg;
401            break;
402        case 'v':
403            printf("remctld %s\n", PACKAGE_VERSION);
404            exit(0);
405            break;
406        default:
407            usage(1);
408            break;
409        }
410    }
411
412    /* Daemonize if told to do so. */
413    if (options.standalone && !options.foreground)
414        daemon(0, options.log_stdout);
415
416    /*
417     * Set up syslog unless stdout/stderr was requested.  Set up debug logging
418     * if requestsed.
419     */
420    if (options.log_stdout) {
421        if (options.debug)
422            message_handlers_debug(1, message_log_stdout);
423    } else {
424        openlog("remctld", LOG_PID | LOG_NDELAY, LOG_DAEMON);
425        message_handlers_notice(1, message_log_syslog_info);
426        message_handlers_warn(1, message_log_syslog_warning);
427        message_handlers_die(1, message_log_syslog_err);
428        if (options.debug)
429            message_handlers_debug(1, message_log_syslog_debug);
430    }
431
432    /* Read the configuration file. */
433    config = server_config_load(options.config_path);
434    if (config == NULL)
435        die("cannot read configuration file %s", options.config_path);
436
437    /*
438     * If a service was specified, we should load only those credentials since
439     * those are the only ones we're allowed to use.  Otherwise, creds will
440     * keep its default value of GSS_C_NO_CREDENTIAL, which means support
441     * anything that's in the keytab.
442     */
443    if (options.service != NULL) {
444        if (!acquire_creds(options.service, &creds))
445            die("unable to acquire creds, aborting");
446    }
447
448    /*
449     * Set up our PID file now after we've daemonized, since we may have
450     * changed PIDs in the process.
451     */
452    if (options.standalone && options.pid_path != NULL) {
453        pid_file = fopen(options.pid_path, "w");
454        if (pid_file == NULL)
455            sysdie("cannot create PID file %s", options.pid_path);
456        fprintf(pid_file, "%ld\n", (long) getpid());
457        fclose(pid_file);
458    }
459
460    /*
461     * If we're not running as a daemon, just process the connection.
462     * Otherwise, create a socket and listen on the socket, processing each
463     * incoming connection.
464     */
465    if (!options.standalone)
466        server_handle_connection(0, config, creds);
467    else
468        server_daemon(&options, config, creds);
469
470    /* Clean up and exit.  We only reach here in regular mode. */
471    if (creds != GSS_C_NO_CREDENTIAL)
472        gss_release_cred(&minor, &creds);
473    return 0;
474}
Note: See TracBrowser for help on using the repository browser.