1N/A/*
1N/A * Copyright (c) 1998-2004, 2006 Sendmail, Inc. and its suppliers.
1N/A * All rights reserved.
1N/A *
1N/A * By using this file, you agree to the terms and conditions set
1N/A * forth in the LICENSE file which can be found at the top level of
1N/A * the sendmail distribution.
1N/A *
1N/A */
1N/A
1N/A#pragma ident "%Z%%M% %I% %E% SMI"
1N/A
1N/A#include <sendmail.h>
1N/A
1N/ASM_RCSID("@(#)$Id: control.c,v 8.128 2006/08/15 23:24:56 ca Exp $")
1N/A
1N/A#include <sm/fdset.h>
1N/A
1N/A/* values for cmd_code */
1N/A#define CMDERROR 0 /* bad command */
1N/A#define CMDRESTART 1 /* restart daemon */
1N/A#define CMDSHUTDOWN 2 /* end daemon */
1N/A#define CMDHELP 3 /* help */
1N/A#define CMDSTATUS 4 /* daemon status */
1N/A#define CMDMEMDUMP 5 /* dump memory, to find memory leaks */
1N/A#define CMDMSTAT 6 /* daemon status, more info, tagged data */
1N/A
1N/Astruct cmd
1N/A{
1N/A char *cmd_name; /* command name */
1N/A int cmd_code; /* internal code, see below */
1N/A};
1N/A
1N/Astatic struct cmd CmdTab[] =
1N/A{
1N/A { "help", CMDHELP },
1N/A { "restart", CMDRESTART },
1N/A { "shutdown", CMDSHUTDOWN },
1N/A { "status", CMDSTATUS },
1N/A { "memdump", CMDMEMDUMP },
1N/A { "mstat", CMDMSTAT },
1N/A { NULL, CMDERROR }
1N/A};
1N/A
1N/A
1N/A
1N/Astatic void controltimeout __P((int));
1N/Aint ControlSocket = -1;
1N/A
1N/A/*
1N/A** OPENCONTROLSOCKET -- create/open the daemon control named socket
1N/A**
1N/A** Creates and opens a named socket for external control over
1N/A** the sendmail daemon.
1N/A**
1N/A** Parameters:
1N/A** none.
1N/A**
1N/A** Returns:
1N/A** 0 if successful, -1 otherwise
1N/A*/
1N/A
1N/Aint
1N/Aopencontrolsocket()
1N/A{
1N/A# if NETUNIX
1N/A int save_errno;
1N/A int rval;
1N/A long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN;
1N/A struct sockaddr_un controladdr;
1N/A
1N/A if (ControlSocketName == NULL || *ControlSocketName == '\0')
1N/A return 0;
1N/A
1N/A if (strlen(ControlSocketName) >= sizeof(controladdr.sun_path))
1N/A {
1N/A errno = ENAMETOOLONG;
1N/A return -1;
1N/A }
1N/A
1N/A rval = safefile(ControlSocketName, RunAsUid, RunAsGid, RunAsUserName,
1N/A sff, S_IRUSR|S_IWUSR, NULL);
1N/A
1N/A /* if not safe, don't create */
1N/A if (rval != 0)
1N/A {
1N/A errno = rval;
1N/A return -1;
1N/A }
1N/A
1N/A ControlSocket = socket(AF_UNIX, SOCK_STREAM, 0);
1N/A if (ControlSocket < 0)
1N/A return -1;
1N/A if (SM_FD_SETSIZE > 0 && ControlSocket >= SM_FD_SETSIZE)
1N/A {
1N/A clrcontrol();
1N/A errno = EINVAL;
1N/A return -1;
1N/A }
1N/A
1N/A (void) unlink(ControlSocketName);
1N/A memset(&controladdr, '\0', sizeof(controladdr));
1N/A controladdr.sun_family = AF_UNIX;
1N/A (void) sm_strlcpy(controladdr.sun_path, ControlSocketName,
1N/A sizeof(controladdr.sun_path));
1N/A
1N/A if (bind(ControlSocket, (struct sockaddr *) &controladdr,
1N/A sizeof(controladdr)) < 0)
1N/A {
1N/A save_errno = errno;
1N/A clrcontrol();
1N/A errno = save_errno;
1N/A return -1;
1N/A }
1N/A
1N/A if (geteuid() == 0)
1N/A {
1N/A uid_t u = 0;
1N/A
1N/A if (RunAsUid != 0)
1N/A u = RunAsUid;
1N/A else if (TrustedUid != 0)
1N/A u = TrustedUid;
1N/A
1N/A if (u != 0 &&
1N/A chown(ControlSocketName, u, -1) < 0)
1N/A {
1N/A save_errno = errno;
1N/A sm_syslog(LOG_ALERT, NOQID,
1N/A "ownership change on %s to uid %d failed: %s",
1N/A ControlSocketName, (int) u,
1N/A sm_errstring(save_errno));
1N/A message("050 ownership change on %s to uid %d failed: %s",
1N/A ControlSocketName, (int) u,
1N/A sm_errstring(save_errno));
1N/A closecontrolsocket(true);
1N/A errno = save_errno;
1N/A return -1;
1N/A }
1N/A }
1N/A
1N/A if (chmod(ControlSocketName, S_IRUSR|S_IWUSR) < 0)
1N/A {
1N/A save_errno = errno;
1N/A closecontrolsocket(true);
1N/A errno = save_errno;
1N/A return -1;
1N/A }
1N/A
1N/A if (listen(ControlSocket, 8) < 0)
1N/A {
1N/A save_errno = errno;
1N/A closecontrolsocket(true);
1N/A errno = save_errno;
1N/A return -1;
1N/A }
1N/A# endif /* NETUNIX */
1N/A return 0;
1N/A}
1N/A/*
1N/A** CLOSECONTROLSOCKET -- close the daemon control named socket
1N/A**
1N/A** Close a named socket.
1N/A**
1N/A** Parameters:
1N/A** fullclose -- if set, close the socket and remove it;
1N/A** otherwise, just remove it
1N/A**
1N/A** Returns:
1N/A** none.
1N/A*/
1N/A
1N/Avoid
1N/Aclosecontrolsocket(fullclose)
1N/A bool fullclose;
1N/A{
1N/A# if NETUNIX
1N/A long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN;
1N/A
1N/A if (ControlSocket >= 0)
1N/A {
1N/A int rval;
1N/A
1N/A if (fullclose)
1N/A {
1N/A (void) close(ControlSocket);
1N/A ControlSocket = -1;
1N/A }
1N/A
1N/A rval = safefile(ControlSocketName, RunAsUid, RunAsGid,
1N/A RunAsUserName, sff, S_IRUSR|S_IWUSR, NULL);
1N/A
1N/A /* if not safe, don't unlink */
1N/A if (rval != 0)
1N/A return;
1N/A
1N/A if (unlink(ControlSocketName) < 0)
1N/A {
1N/A sm_syslog(LOG_WARNING, NOQID,
1N/A "Could not remove control socket: %s",
1N/A sm_errstring(errno));
1N/A return;
1N/A }
1N/A }
1N/A# endif /* NETUNIX */
1N/A return;
1N/A}
1N/A/*
1N/A** CLRCONTROL -- reset the control connection
1N/A**
1N/A** Parameters:
1N/A** none.
1N/A**
1N/A** Returns:
1N/A** none.
1N/A**
1N/A** Side Effects:
1N/A** releases any resources used by the control interface.
1N/A*/
1N/A
1N/Avoid
1N/Aclrcontrol()
1N/A{
1N/A# if NETUNIX
1N/A if (ControlSocket >= 0)
1N/A (void) close(ControlSocket);
1N/A ControlSocket = -1;
1N/A# endif /* NETUNIX */
1N/A}
1N/A/*
1N/A** CONTROL_COMMAND -- read and process command from named socket
1N/A**
1N/A** Read and process the command from the opened socket.
1N/A** Exits when done since it is running in a forked child.
1N/A**
1N/A** Parameters:
1N/A** sock -- the opened socket from getrequests()
1N/A** e -- the current envelope
1N/A**
1N/A** Returns:
1N/A** none.
1N/A*/
1N/A
1N/Astatic jmp_buf CtxControlTimeout;
1N/A
1N/A/* ARGSUSED0 */
1N/Astatic void
1N/Acontroltimeout(timeout)
1N/A int timeout;
1N/A{
1N/A /*
1N/A ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
1N/A ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
1N/A ** DOING.
1N/A */
1N/A
1N/A errno = ETIMEDOUT;
1N/A longjmp(CtxControlTimeout, 1);
1N/A}
1N/A
1N/Avoid
1N/Acontrol_command(sock, e)
1N/A int sock;
1N/A ENVELOPE *e;
1N/A{
1N/A volatile int exitstat = EX_OK;
1N/A SM_FILE_T *s = NULL;
1N/A SM_EVENT *ev = NULL;
1N/A SM_FILE_T *traffic;
1N/A SM_FILE_T *oldout;
1N/A char *cmd;
1N/A char *p;
1N/A struct cmd *c;
1N/A char cmdbuf[MAXLINE];
1N/A char inp[MAXLINE];
1N/A
1N/A sm_setproctitle(false, e, "control cmd read");
1N/A
1N/A if (TimeOuts.to_control > 0)
1N/A {
1N/A /* handle possible input timeout */
1N/A if (setjmp(CtxControlTimeout) != 0)
1N/A {
1N/A if (LogLevel > 2)
1N/A sm_syslog(LOG_NOTICE, e->e_id,
1N/A "timeout waiting for input during control command");
1N/A exit(EX_IOERR);
1N/A }
1N/A ev = sm_setevent(TimeOuts.to_control, controltimeout,
1N/A TimeOuts.to_control);
1N/A }
1N/A
1N/A s = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, (void *) &sock,
1N/A SM_IO_RDWR, NULL);
1N/A if (s == NULL)
1N/A {
1N/A int save_errno = errno;
1N/A
1N/A (void) close(sock);
1N/A errno = save_errno;
1N/A exit(EX_IOERR);
1N/A }
1N/A (void) sm_io_setvbuf(s, SM_TIME_DEFAULT, NULL,
1N/A SM_IO_NBF, SM_IO_BUFSIZ);
1N/A
1N/A if (sm_io_fgets(s, SM_TIME_DEFAULT, inp, sizeof(inp)) == NULL)
1N/A {
1N/A (void) sm_io_close(s, SM_TIME_DEFAULT);
1N/A exit(EX_IOERR);
1N/A }
1N/A (void) sm_io_flush(s, SM_TIME_DEFAULT);
1N/A
1N/A /* clean up end of line */
1N/A fixcrlf(inp, true);
1N/A
1N/A sm_setproctitle(false, e, "control: %s", inp);
1N/A
1N/A /* break off command */
1N/A for (p = inp; isascii(*p) && isspace(*p); p++)
1N/A continue;
1N/A cmd = cmdbuf;
1N/A while (*p != '\0' &&
1N/A !(isascii(*p) && isspace(*p)) &&
1N/A cmd < &cmdbuf[sizeof(cmdbuf) - 2])
1N/A *cmd++ = *p++;
1N/A *cmd = '\0';
1N/A
1N/A /* throw away leading whitespace */
1N/A while (isascii(*p) && isspace(*p))
1N/A p++;
1N/A
1N/A /* decode command */
1N/A for (c = CmdTab; c->cmd_name != NULL; c++)
1N/A {
1N/A if (sm_strcasecmp(c->cmd_name, cmdbuf) == 0)
1N/A break;
1N/A }
1N/A
1N/A switch (c->cmd_code)
1N/A {
1N/A case CMDHELP: /* get help */
1N/A traffic = TrafficLogFile;
1N/A TrafficLogFile = NULL;
1N/A oldout = OutChannel;
1N/A OutChannel = s;
1N/A help("control", e);
1N/A TrafficLogFile = traffic;
1N/A OutChannel = oldout;
1N/A break;
1N/A
1N/A case CMDRESTART: /* restart the daemon */
1N/A (void) sm_io_fprintf(s, SM_TIME_DEFAULT, "OK\r\n");
1N/A exitstat = EX_RESTART;
1N/A break;
1N/A
1N/A case CMDSHUTDOWN: /* kill the daemon */
1N/A (void) sm_io_fprintf(s, SM_TIME_DEFAULT, "OK\r\n");
1N/A exitstat = EX_SHUTDOWN;
1N/A break;
1N/A
1N/A case CMDSTATUS: /* daemon status */
1N/A proc_list_probe();
1N/A {
1N/A int qgrp;
1N/A long bsize;
1N/A long free;
1N/A
1N/A /* XXX need to deal with different partitions */
1N/A qgrp = e->e_qgrp;
1N/A if (!ISVALIDQGRP(qgrp))
1N/A qgrp = 0;
1N/A free = freediskspace(Queue[qgrp]->qg_qdir, &bsize);
1N/A
1N/A /*
1N/A ** Prevent overflow and don't lose
1N/A ** precision (if bsize == 512)
1N/A */
1N/A
1N/A if (free > 0)
1N/A free = (long)((double) free *
1N/A ((double) bsize / 1024));
1N/A
1N/A (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
1N/A "%d/%d/%ld/%d\r\n",
1N/A CurChildren, MaxChildren,
1N/A free, getla());
1N/A }
1N/A proc_list_display(s, "");
1N/A break;
1N/A
1N/A case CMDMSTAT: /* daemon status, extended, tagged format */
1N/A proc_list_probe();
1N/A (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
1N/A "C:%d\r\nM:%d\r\nL:%d\r\n",
1N/A CurChildren, MaxChildren,
1N/A getla());
1N/A printnqe(s, "Q:");
1N/A disk_status(s, "D:");
1N/A proc_list_display(s, "P:");
1N/A break;
1N/A
1N/A case CMDMEMDUMP: /* daemon memory dump, to find memory leaks */
1N/A# if SM_HEAP_CHECK
1N/A /* dump the heap, if we are checking for memory leaks */
1N/A if (sm_debug_active(&SmHeapCheck, 2))
1N/A {
1N/A sm_heap_report(s, sm_debug_level(&SmHeapCheck) - 1);
1N/A }
1N/A else
1N/A {
1N/A (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
1N/A "Memory dump unavailable.\r\n");
1N/A (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
1N/A "To fix, run sendmail with -dsm_check_heap.4\r\n");
1N/A }
1N/A# else /* SM_HEAP_CHECK */
1N/A (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
1N/A "Memory dump unavailable.\r\n");
1N/A (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
1N/A "To fix, rebuild with -DSM_HEAP_CHECK\r\n");
1N/A# endif /* SM_HEAP_CHECK */
1N/A break;
1N/A
1N/A case CMDERROR: /* unknown command */
1N/A (void) sm_io_fprintf(s, SM_TIME_DEFAULT,
1N/A "Bad command (%s)\r\n", cmdbuf);
1N/A break;
1N/A }
1N/A (void) sm_io_close(s, SM_TIME_DEFAULT);
1N/A if (ev != NULL)
1N/A sm_clrevent(ev);
1N/A exit(exitstat);
1N/A}