/* * 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. * */ #pragma ident "%Z%%M% %I% %E% SMI" #include SM_RCSID("@(#)$Id: control.c,v 8.128 2006/08/15 23:24:56 ca Exp $") #include /* 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); }