root/usr/src/cmd/sendmail/src/control.c
/*
 * Copyright (c) 1998-2004, 2006 Sendmail, Inc. and its suppliers.
 *      All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 *
 */

#include <sendmail.h>

SM_RCSID("@(#)$Id: control.c,v 8.128 2006/08/15 23:24:56 ca Exp $")

#include <sm/fdset.h>

/* values for cmd_code */
#define CMDERROR        0       /* bad command */
#define CMDRESTART      1       /* restart daemon */
#define CMDSHUTDOWN     2       /* end daemon */
#define CMDHELP         3       /* help */
#define CMDSTATUS       4       /* daemon status */
#define CMDMEMDUMP      5       /* dump memory, to find memory leaks */
#define CMDMSTAT        6       /* daemon status, more info, tagged data */

struct cmd
{
        char    *cmd_name;      /* command name */
        int     cmd_code;       /* internal code, see below */
};

static struct cmd       CmdTab[] =
{
        { "help",       CMDHELP         },
        { "restart",    CMDRESTART      },
        { "shutdown",   CMDSHUTDOWN     },
        { "status",     CMDSTATUS       },
        { "memdump",    CMDMEMDUMP      },
        { "mstat",      CMDMSTAT        },
        { NULL,         CMDERROR        }
};



static void     controltimeout __P((int));
int ControlSocket = -1;

/*
**  OPENCONTROLSOCKET -- create/open the daemon control named socket
**
**      Creates and opens a named socket for external control over
**      the sendmail daemon.
**
**      Parameters:
**              none.
**
**      Returns:
**              0 if successful, -1 otherwise
*/

int
opencontrolsocket()
{
# if NETUNIX
        int save_errno;
        int rval;
        long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN;
        struct sockaddr_un controladdr;

        if (ControlSocketName == NULL || *ControlSocketName == '\0')
                return 0;

        if (strlen(ControlSocketName) >= sizeof(controladdr.sun_path))
        {
                errno = ENAMETOOLONG;
                return -1;
        }

        rval = safefile(ControlSocketName, RunAsUid, RunAsGid, RunAsUserName,
                        sff, S_IRUSR|S_IWUSR, NULL);

        /* if not safe, don't create */
        if (rval != 0)
        {
                errno = rval;
                return -1;
        }

        ControlSocket = socket(AF_UNIX, SOCK_STREAM, 0);
        if (ControlSocket < 0)
                return -1;
        if (SM_FD_SETSIZE > 0 && ControlSocket >= SM_FD_SETSIZE)
        {
                clrcontrol();
                errno = EINVAL;
                return -1;
        }

        (void) unlink(ControlSocketName);
        memset(&controladdr, '\0', sizeof(controladdr));
        controladdr.sun_family = AF_UNIX;
        (void) sm_strlcpy(controladdr.sun_path, ControlSocketName,
                          sizeof(controladdr.sun_path));

        if (bind(ControlSocket, (struct sockaddr *) &controladdr,
                 sizeof(controladdr)) < 0)
        {
                save_errno = errno;
                clrcontrol();
                errno = save_errno;
                return -1;
        }

        if (geteuid() == 0)
        {
                uid_t u = 0;

                if (RunAsUid != 0)
                        u = RunAsUid;
                else if (TrustedUid != 0)
                        u = TrustedUid;

                if (u != 0 &&
                    chown(ControlSocketName, u, -1) < 0)
                {
                        save_errno = errno;
                        sm_syslog(LOG_ALERT, NOQID,
                                  "ownership change on %s to uid %d failed: %s",
                                  ControlSocketName, (int) u,
                                  sm_errstring(save_errno));
                        message("050 ownership change on %s to uid %d failed: %s",
                                ControlSocketName, (int) u,
                                sm_errstring(save_errno));
                        closecontrolsocket(true);
                        errno = save_errno;
                        return -1;
                }
        }

        if (chmod(ControlSocketName, S_IRUSR|S_IWUSR) < 0)
        {
                save_errno = errno;
                closecontrolsocket(true);
                errno = save_errno;
                return -1;
        }

        if (listen(ControlSocket, 8) < 0)
        {
                save_errno = errno;
                closecontrolsocket(true);
                errno = save_errno;
                return -1;
        }
# endif /* NETUNIX */
        return 0;
}
/*
**  CLOSECONTROLSOCKET -- close the daemon control named socket
**
**      Close a named socket.
**
**      Parameters:
**              fullclose -- if set, close the socket and remove it;
**                           otherwise, just remove it
**
**      Returns:
**              none.
*/

void
closecontrolsocket(fullclose)
        bool fullclose;
{
# if NETUNIX
        long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN;

        if (ControlSocket >= 0)
        {
                int rval;

                if (fullclose)
                {
                        (void) close(ControlSocket);
                        ControlSocket = -1;
                }

                rval = safefile(ControlSocketName, RunAsUid, RunAsGid,
                                RunAsUserName, sff, S_IRUSR|S_IWUSR, NULL);

                /* if not safe, don't unlink */
                if (rval != 0)
                        return;

                if (unlink(ControlSocketName) < 0)
                {
                        sm_syslog(LOG_WARNING, NOQID,
                                  "Could not remove control socket: %s",
                                  sm_errstring(errno));
                        return;
                }
        }
# endif /* NETUNIX */
        return;
}
/*
**  CLRCONTROL -- reset the control connection
**
**      Parameters:
**              none.
**
**      Returns:
**              none.
**
**      Side Effects:
**              releases any resources used by the control interface.
*/

void
clrcontrol()
{
# if NETUNIX
        if (ControlSocket >= 0)
                (void) close(ControlSocket);
        ControlSocket = -1;
# endif /* NETUNIX */
}
/*
**  CONTROL_COMMAND -- read and process command from named socket
**
**      Read and process the command from the opened socket.
**      Exits when done since it is running in a forked child.
**
**      Parameters:
**              sock -- the opened socket from getrequests()
**              e -- the current envelope
**
**      Returns:
**              none.
*/

static jmp_buf  CtxControlTimeout;

/* ARGSUSED0 */
static void
controltimeout(timeout)
        int timeout;
{
        /*
        **  NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
        **      ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
        **      DOING.
        */

        errno = ETIMEDOUT;
        longjmp(CtxControlTimeout, 1);
}

void
control_command(sock, e)
        int sock;
        ENVELOPE *e;
{
        volatile int exitstat = EX_OK;
        SM_FILE_T *s = NULL;
        SM_EVENT *ev = NULL;
        SM_FILE_T *traffic;
        SM_FILE_T *oldout;
        char *cmd;
        char *p;
        struct cmd *c;
        char cmdbuf[MAXLINE];
        char inp[MAXLINE];

        sm_setproctitle(false, e, "control cmd read");

        if (TimeOuts.to_control > 0)
        {
                /* handle possible input timeout */
                if (setjmp(CtxControlTimeout) != 0)
                {
                        if (LogLevel > 2)
                                sm_syslog(LOG_NOTICE, e->e_id,
                                          "timeout waiting for input during control command");
                        exit(EX_IOERR);
                }
                ev = sm_setevent(TimeOuts.to_control, controltimeout,
                                 TimeOuts.to_control);
        }

        s = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, (void *) &sock,
                       SM_IO_RDWR, NULL);
        if (s == NULL)
        {
                int save_errno = errno;

                (void) close(sock);
                errno = save_errno;
                exit(EX_IOERR);
        }
        (void) sm_io_setvbuf(s, SM_TIME_DEFAULT, NULL,
                             SM_IO_NBF, SM_IO_BUFSIZ);

        if (sm_io_fgets(s, SM_TIME_DEFAULT, inp, sizeof(inp)) == NULL)
        {
                (void) sm_io_close(s, SM_TIME_DEFAULT);
                exit(EX_IOERR);
        }
        (void) sm_io_flush(s, SM_TIME_DEFAULT);

        /* clean up end of line */
        fixcrlf(inp, true);

        sm_setproctitle(false, e, "control: %s", inp);

        /* break off command */
        for (p = inp; isascii(*p) && isspace(*p); p++)
                continue;
        cmd = cmdbuf;
        while (*p != '\0' &&
               !(isascii(*p) && isspace(*p)) &&
               cmd < &cmdbuf[sizeof(cmdbuf) - 2])
                *cmd++ = *p++;
        *cmd = '\0';

        /* throw away leading whitespace */
        while (isascii(*p) && isspace(*p))
                p++;

        /* decode command */
        for (c = CmdTab; c->cmd_name != NULL; c++)
        {
                if (sm_strcasecmp(c->cmd_name, cmdbuf) == 0)
                        break;
        }

        switch (c->cmd_code)
        {
          case CMDHELP:         /* get help */
                traffic = TrafficLogFile;
                TrafficLogFile = NULL;
                oldout = OutChannel;
                OutChannel = s;
                help("control", e);
                TrafficLogFile = traffic;
                OutChannel = oldout;
                break;

          case CMDRESTART:      /* restart the daemon */
                (void) sm_io_fprintf(s, SM_TIME_DEFAULT, "OK\r\n");
                exitstat = EX_RESTART;
                break;

          case CMDSHUTDOWN:     /* kill the daemon */
                (void) sm_io_fprintf(s, SM_TIME_DEFAULT, "OK\r\n");
                exitstat = EX_SHUTDOWN;
                break;

          case CMDSTATUS:       /* daemon status */
                proc_list_probe();
                {
                        int qgrp;
                        long bsize;
                        long free;

                        /* XXX need to deal with different partitions */
                        qgrp = e->e_qgrp;
                        if (!ISVALIDQGRP(qgrp))
                                qgrp = 0;
                        free = freediskspace(Queue[qgrp]->qg_qdir, &bsize);

                        /*
                        **  Prevent overflow and don't lose
                        **  precision (if bsize == 512)
                        */

                        if (free > 0)
                                free = (long)((double) free *
                                              ((double) bsize / 1024));

                        (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
                                             "%d/%d/%ld/%d\r\n",
                                             CurChildren, MaxChildren,
                                             free, getla());
                }
                proc_list_display(s, "");
                break;

          case CMDMSTAT:        /* daemon status, extended, tagged format */
                proc_list_probe();
                (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
                                     "C:%d\r\nM:%d\r\nL:%d\r\n",
                                     CurChildren, MaxChildren,
                                     getla());
                printnqe(s, "Q:");
                disk_status(s, "D:");
                proc_list_display(s, "P:");
                break;

          case CMDMEMDUMP:      /* daemon memory dump, to find memory leaks */
# if SM_HEAP_CHECK
                /* dump the heap, if we are checking for memory leaks */
                if (sm_debug_active(&SmHeapCheck, 2))
                {
                        sm_heap_report(s, sm_debug_level(&SmHeapCheck) - 1);
                }
                else
                {
                        (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
                                             "Memory dump unavailable.\r\n");
                        (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
                                             "To fix, run sendmail with -dsm_check_heap.4\r\n");
                }
# else /* SM_HEAP_CHECK */
                (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
                                     "Memory dump unavailable.\r\n");
                (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
                                     "To fix, rebuild with -DSM_HEAP_CHECK\r\n");
# endif /* SM_HEAP_CHECK */
                break;

          case CMDERROR:        /* unknown command */
                (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
                                     "Bad command (%s)\r\n", cmdbuf);
                break;
        }
        (void) sm_io_close(s, SM_TIME_DEFAULT);
        if (ev != NULL)
                sm_clrevent(ev);
        exit(exitstat);
}