142575Speter/* 2261370Sgshapiro * Copyright (c) 1998-2004, 2006 Proofpoint, Inc. and its suppliers. 364562Sgshapiro * All rights reserved. 442575Speter * 542575Speter * By using this file, you agree to the terms and conditions set 642575Speter * forth in the LICENSE file which can be found at the top level of 742575Speter * the sendmail distribution. 842575Speter * 942575Speter */ 1042575Speter 1164562Sgshapiro#include <sendmail.h> 1242575Speter 13266711SgshapiroSM_RCSID("@(#)$Id: control.c,v 8.130 2013-11-22 20:51:55 ca Exp $") 1490792Sgshapiro 15110560Sgshapiro#include <sm/fdset.h> 16110560Sgshapiro 1777349Sgshapiro/* values for cmd_code */ 1890792Sgshapiro#define CMDERROR 0 /* bad command */ 1990792Sgshapiro#define CMDRESTART 1 /* restart daemon */ 2090792Sgshapiro#define CMDSHUTDOWN 2 /* end daemon */ 2190792Sgshapiro#define CMDHELP 3 /* help */ 2290792Sgshapiro#define CMDSTATUS 4 /* daemon status */ 2390792Sgshapiro#define CMDMEMDUMP 5 /* dump memory, to find memory leaks */ 24168515Sgshapiro#define CMDMSTAT 6 /* daemon status, more info, tagged data */ 2564562Sgshapiro 2677349Sgshapirostruct cmd 2777349Sgshapiro{ 2877349Sgshapiro char *cmd_name; /* command name */ 2977349Sgshapiro int cmd_code; /* internal code, see below */ 3077349Sgshapiro}; 3177349Sgshapiro 3277349Sgshapirostatic struct cmd CmdTab[] = 3377349Sgshapiro{ 3477349Sgshapiro { "help", CMDHELP }, 3577349Sgshapiro { "restart", CMDRESTART }, 3677349Sgshapiro { "shutdown", CMDSHUTDOWN }, 3777349Sgshapiro { "status", CMDSTATUS }, 3890792Sgshapiro { "memdump", CMDMEMDUMP }, 3990792Sgshapiro { "mstat", CMDMSTAT }, 4077349Sgshapiro { NULL, CMDERROR } 4177349Sgshapiro}; 4277349Sgshapiro 4377349Sgshapiro 4490792Sgshapiro 45141858Sgshapirostatic void controltimeout __P((int)); 4642575Speterint ControlSocket = -1; 4742575Speter 4890792Sgshapiro/* 4942575Speter** OPENCONTROLSOCKET -- create/open the daemon control named socket 5042575Speter** 5142575Speter** Creates and opens a named socket for external control over 5242575Speter** the sendmail daemon. 5342575Speter** 5442575Speter** Parameters: 5542575Speter** none. 5642575Speter** 5742575Speter** Returns: 5842575Speter** 0 if successful, -1 otherwise 5942575Speter*/ 6042575Speter 6142575Speterint 6242575Speteropencontrolsocket() 6342575Speter{ 6490792Sgshapiro# if NETUNIX 6564562Sgshapiro int save_errno; 6642575Speter int rval; 6764562Sgshapiro long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN; 6842575Speter struct sockaddr_un controladdr; 6942575Speter 7090792Sgshapiro if (ControlSocketName == NULL || *ControlSocketName == '\0') 7142575Speter return 0; 7242575Speter 73168515Sgshapiro if (strlen(ControlSocketName) >= sizeof(controladdr.sun_path)) 7442575Speter { 7542575Speter errno = ENAMETOOLONG; 7642575Speter return -1; 7742575Speter } 7842575Speter 7942575Speter rval = safefile(ControlSocketName, RunAsUid, RunAsGid, RunAsUserName, 8042575Speter sff, S_IRUSR|S_IWUSR, NULL); 8142575Speter 8242575Speter /* if not safe, don't create */ 8342575Speter if (rval != 0) 8442575Speter { 8542575Speter errno = rval; 8642575Speter return -1; 8742575Speter } 8842575Speter 8942575Speter ControlSocket = socket(AF_UNIX, SOCK_STREAM, 0); 9042575Speter if (ControlSocket < 0) 9142575Speter return -1; 92110560Sgshapiro if (SM_FD_SETSIZE > 0 && ControlSocket >= SM_FD_SETSIZE) 93110560Sgshapiro { 94110560Sgshapiro clrcontrol(); 95110560Sgshapiro errno = EINVAL; 96110560Sgshapiro return -1; 97110560Sgshapiro } 9842575Speter 9964562Sgshapiro (void) unlink(ControlSocketName); 100168515Sgshapiro memset(&controladdr, '\0', sizeof(controladdr)); 10142575Speter controladdr.sun_family = AF_UNIX; 10290792Sgshapiro (void) sm_strlcpy(controladdr.sun_path, ControlSocketName, 103168515Sgshapiro sizeof(controladdr.sun_path)); 10442575Speter 10542575Speter if (bind(ControlSocket, (struct sockaddr *) &controladdr, 106168515Sgshapiro sizeof(controladdr)) < 0) 10742575Speter { 10864562Sgshapiro save_errno = errno; 10943730Speter clrcontrol(); 11042575Speter errno = save_errno; 11142575Speter return -1; 11242575Speter } 11342575Speter 11471345Sgshapiro if (geteuid() == 0) 11542575Speter { 11671345Sgshapiro uid_t u = 0; 11771345Sgshapiro 11871345Sgshapiro if (RunAsUid != 0) 11971345Sgshapiro u = RunAsUid; 12071345Sgshapiro else if (TrustedUid != 0) 12171345Sgshapiro u = TrustedUid; 12271345Sgshapiro 12371345Sgshapiro if (u != 0 && 12471345Sgshapiro chown(ControlSocketName, u, -1) < 0) 12542575Speter { 12664562Sgshapiro save_errno = errno; 12742575Speter sm_syslog(LOG_ALERT, NOQID, 12871345Sgshapiro "ownership change on %s to uid %d failed: %s", 12971345Sgshapiro ControlSocketName, (int) u, 13090792Sgshapiro sm_errstring(save_errno)); 13171345Sgshapiro message("050 ownership change on %s to uid %d failed: %s", 13271345Sgshapiro ControlSocketName, (int) u, 13390792Sgshapiro sm_errstring(save_errno)); 13490792Sgshapiro closecontrolsocket(true); 13542575Speter errno = save_errno; 13642575Speter return -1; 13742575Speter } 13842575Speter } 13942575Speter 14042575Speter if (chmod(ControlSocketName, S_IRUSR|S_IWUSR) < 0) 14142575Speter { 14264562Sgshapiro save_errno = errno; 14390792Sgshapiro closecontrolsocket(true); 14442575Speter errno = save_errno; 14542575Speter return -1; 14642575Speter } 14742575Speter 14842575Speter if (listen(ControlSocket, 8) < 0) 14942575Speter { 15064562Sgshapiro save_errno = errno; 15190792Sgshapiro closecontrolsocket(true); 15242575Speter errno = save_errno; 15342575Speter return -1; 15442575Speter } 15590792Sgshapiro# endif /* NETUNIX */ 15642575Speter return 0; 15742575Speter} 15890792Sgshapiro/* 15942575Speter** CLOSECONTROLSOCKET -- close the daemon control named socket 16042575Speter** 16142575Speter** Close a named socket. 16242575Speter** 16342575Speter** Parameters: 16442575Speter** fullclose -- if set, close the socket and remove it; 16542575Speter** otherwise, just remove it 16642575Speter** 16742575Speter** Returns: 16842575Speter** none. 16942575Speter*/ 17042575Speter 17142575Spetervoid 17242575Speterclosecontrolsocket(fullclose) 17342575Speter bool fullclose; 17442575Speter{ 17590792Sgshapiro# if NETUNIX 17664562Sgshapiro long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN; 17742575Speter 17842575Speter if (ControlSocket >= 0) 17942575Speter { 18042575Speter int rval; 18142575Speter 18242575Speter if (fullclose) 18342575Speter { 18442575Speter (void) close(ControlSocket); 18542575Speter ControlSocket = -1; 18642575Speter } 18742575Speter 18871345Sgshapiro rval = safefile(ControlSocketName, RunAsUid, RunAsGid, 18971345Sgshapiro RunAsUserName, sff, S_IRUSR|S_IWUSR, NULL); 19064562Sgshapiro 19142575Speter /* if not safe, don't unlink */ 19242575Speter if (rval != 0) 19342575Speter return; 19442575Speter 19542575Speter if (unlink(ControlSocketName) < 0) 19642575Speter { 19742575Speter sm_syslog(LOG_WARNING, NOQID, 19842575Speter "Could not remove control socket: %s", 19990792Sgshapiro sm_errstring(errno)); 20042575Speter return; 20142575Speter } 20242575Speter } 20390792Sgshapiro# endif /* NETUNIX */ 20442575Speter return; 20542575Speter} 20690792Sgshapiro/* 20742575Speter** CLRCONTROL -- reset the control connection 20842575Speter** 20942575Speter** Parameters: 21042575Speter** none. 21142575Speter** 21242575Speter** Returns: 21342575Speter** none. 21442575Speter** 21542575Speter** Side Effects: 21642575Speter** releases any resources used by the control interface. 21742575Speter*/ 21842575Speter 21942575Spetervoid 22042575Speterclrcontrol() 22142575Speter{ 22290792Sgshapiro# if NETUNIX 22342575Speter if (ControlSocket >= 0) 22442575Speter (void) close(ControlSocket); 22542575Speter ControlSocket = -1; 22690792Sgshapiro# endif /* NETUNIX */ 22742575Speter} 22890792Sgshapiro/* 22942575Speter** CONTROL_COMMAND -- read and process command from named socket 23042575Speter** 23142575Speter** Read and process the command from the opened socket. 23264562Sgshapiro** Exits when done since it is running in a forked child. 23342575Speter** 23442575Speter** Parameters: 23542575Speter** sock -- the opened socket from getrequests() 23642575Speter** e -- the current envelope 23742575Speter** 23842575Speter** Returns: 23942575Speter** none. 24042575Speter*/ 24142575Speter 24264562Sgshapirostatic jmp_buf CtxControlTimeout; 24364562Sgshapiro 24490792Sgshapiro/* ARGSUSED0 */ 24564562Sgshapirostatic void 24664562Sgshapirocontroltimeout(timeout) 247141858Sgshapiro int timeout; 24864562Sgshapiro{ 24977349Sgshapiro /* 25077349Sgshapiro ** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD 25177349Sgshapiro ** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE 25277349Sgshapiro ** DOING. 25377349Sgshapiro */ 25477349Sgshapiro 25577349Sgshapiro errno = ETIMEDOUT; 25664562Sgshapiro longjmp(CtxControlTimeout, 1); 25764562Sgshapiro} 25864562Sgshapiro 25942575Spetervoid 26042575Spetercontrol_command(sock, e) 26142575Speter int sock; 26242575Speter ENVELOPE *e; 26342575Speter{ 26464562Sgshapiro volatile int exitstat = EX_OK; 26590792Sgshapiro SM_FILE_T *s = NULL; 26690792Sgshapiro SM_EVENT *ev = NULL; 26790792Sgshapiro SM_FILE_T *traffic; 26890792Sgshapiro SM_FILE_T *oldout; 26942575Speter char *cmd; 27042575Speter char *p; 27142575Speter struct cmd *c; 27242575Speter char cmdbuf[MAXLINE]; 27342575Speter char inp[MAXLINE]; 27442575Speter 27590792Sgshapiro sm_setproctitle(false, e, "control cmd read"); 27664562Sgshapiro 27764562Sgshapiro if (TimeOuts.to_control > 0) 27864562Sgshapiro { 27964562Sgshapiro /* handle possible input timeout */ 28064562Sgshapiro if (setjmp(CtxControlTimeout) != 0) 28164562Sgshapiro { 28264562Sgshapiro if (LogLevel > 2) 28364562Sgshapiro sm_syslog(LOG_NOTICE, e->e_id, 28464562Sgshapiro "timeout waiting for input during control command"); 28564562Sgshapiro exit(EX_IOERR); 28664562Sgshapiro } 28790792Sgshapiro ev = sm_setevent(TimeOuts.to_control, controltimeout, 28890792Sgshapiro TimeOuts.to_control); 28964562Sgshapiro } 29064562Sgshapiro 29190792Sgshapiro s = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, (void *) &sock, 29290792Sgshapiro SM_IO_RDWR, NULL); 29342575Speter if (s == NULL) 29442575Speter { 29542575Speter int save_errno = errno; 29642575Speter 29764562Sgshapiro (void) close(sock); 29842575Speter errno = save_errno; 29964562Sgshapiro exit(EX_IOERR); 30042575Speter } 30190792Sgshapiro (void) sm_io_setvbuf(s, SM_TIME_DEFAULT, NULL, 30290792Sgshapiro SM_IO_NBF, SM_IO_BUFSIZ); 30342575Speter 304249865Sgshapiro if (sm_io_fgets(s, SM_TIME_DEFAULT, inp, sizeof(inp)) < 0) 30542575Speter { 30690792Sgshapiro (void) sm_io_close(s, SM_TIME_DEFAULT); 30764562Sgshapiro exit(EX_IOERR); 30842575Speter } 30990792Sgshapiro (void) sm_io_flush(s, SM_TIME_DEFAULT); 31042575Speter 31142575Speter /* clean up end of line */ 31290792Sgshapiro fixcrlf(inp, true); 31342575Speter 31490792Sgshapiro sm_setproctitle(false, e, "control: %s", inp); 31542575Speter 31642575Speter /* break off command */ 31742575Speter for (p = inp; isascii(*p) && isspace(*p); p++) 31842575Speter continue; 31942575Speter cmd = cmdbuf; 32042575Speter while (*p != '\0' && 32142575Speter !(isascii(*p) && isspace(*p)) && 322168515Sgshapiro cmd < &cmdbuf[sizeof(cmdbuf) - 2]) 32342575Speter *cmd++ = *p++; 32442575Speter *cmd = '\0'; 32564562Sgshapiro 32642575Speter /* throw away leading whitespace */ 32742575Speter while (isascii(*p) && isspace(*p)) 32842575Speter p++; 32964562Sgshapiro 33042575Speter /* decode command */ 33164562Sgshapiro for (c = CmdTab; c->cmd_name != NULL; c++) 33242575Speter { 33390792Sgshapiro if (sm_strcasecmp(c->cmd_name, cmdbuf) == 0) 33442575Speter break; 33542575Speter } 33642575Speter 33764562Sgshapiro switch (c->cmd_code) 33842575Speter { 33942575Speter case CMDHELP: /* get help */ 34042575Speter traffic = TrafficLogFile; 34142575Speter TrafficLogFile = NULL; 34242575Speter oldout = OutChannel; 34342575Speter OutChannel = s; 34464562Sgshapiro help("control", e); 34542575Speter TrafficLogFile = traffic; 34642575Speter OutChannel = oldout; 34742575Speter break; 34864562Sgshapiro 34942575Speter case CMDRESTART: /* restart the daemon */ 35090792Sgshapiro (void) sm_io_fprintf(s, SM_TIME_DEFAULT, "OK\r\n"); 35164562Sgshapiro exitstat = EX_RESTART; 35242575Speter break; 35342575Speter 35442575Speter case CMDSHUTDOWN: /* kill the daemon */ 35590792Sgshapiro (void) sm_io_fprintf(s, SM_TIME_DEFAULT, "OK\r\n"); 35664562Sgshapiro exitstat = EX_SHUTDOWN; 35742575Speter break; 35842575Speter 35942575Speter case CMDSTATUS: /* daemon status */ 36042575Speter proc_list_probe(); 36164562Sgshapiro { 36290792Sgshapiro int qgrp; 36364562Sgshapiro long bsize; 36464562Sgshapiro long free; 36564562Sgshapiro 36690792Sgshapiro /* XXX need to deal with different partitions */ 36790792Sgshapiro qgrp = e->e_qgrp; 36890792Sgshapiro if (!ISVALIDQGRP(qgrp)) 36990792Sgshapiro qgrp = 0; 37090792Sgshapiro free = freediskspace(Queue[qgrp]->qg_qdir, &bsize); 37164562Sgshapiro 37264562Sgshapiro /* 37364562Sgshapiro ** Prevent overflow and don't lose 37464562Sgshapiro ** precision (if bsize == 512) 37564562Sgshapiro */ 37664562Sgshapiro 37790792Sgshapiro if (free > 0) 37890792Sgshapiro free = (long)((double) free * 37990792Sgshapiro ((double) bsize / 1024)); 38064562Sgshapiro 38190792Sgshapiro (void) sm_io_fprintf(s, SM_TIME_DEFAULT, 38290792Sgshapiro "%d/%d/%ld/%d\r\n", 38390792Sgshapiro CurChildren, MaxChildren, 38490792Sgshapiro free, getla()); 38564562Sgshapiro } 38690792Sgshapiro proc_list_display(s, ""); 38742575Speter break; 38842575Speter 38990792Sgshapiro case CMDMSTAT: /* daemon status, extended, tagged format */ 39090792Sgshapiro proc_list_probe(); 39190792Sgshapiro (void) sm_io_fprintf(s, SM_TIME_DEFAULT, 39290792Sgshapiro "C:%d\r\nM:%d\r\nL:%d\r\n", 39390792Sgshapiro CurChildren, MaxChildren, 39490792Sgshapiro getla()); 39590792Sgshapiro printnqe(s, "Q:"); 39690792Sgshapiro disk_status(s, "D:"); 39790792Sgshapiro proc_list_display(s, "P:"); 39890792Sgshapiro break; 39990792Sgshapiro 40090792Sgshapiro case CMDMEMDUMP: /* daemon memory dump, to find memory leaks */ 40190792Sgshapiro# if SM_HEAP_CHECK 40290792Sgshapiro /* dump the heap, if we are checking for memory leaks */ 40390792Sgshapiro if (sm_debug_active(&SmHeapCheck, 2)) 40490792Sgshapiro { 40590792Sgshapiro sm_heap_report(s, sm_debug_level(&SmHeapCheck) - 1); 40690792Sgshapiro } 40790792Sgshapiro else 40890792Sgshapiro { 40990792Sgshapiro (void) sm_io_fprintf(s, SM_TIME_DEFAULT, 41090792Sgshapiro "Memory dump unavailable.\r\n"); 41190792Sgshapiro (void) sm_io_fprintf(s, SM_TIME_DEFAULT, 41290792Sgshapiro "To fix, run sendmail with -dsm_check_heap.4\r\n"); 41390792Sgshapiro } 41490792Sgshapiro# else /* SM_HEAP_CHECK */ 41590792Sgshapiro (void) sm_io_fprintf(s, SM_TIME_DEFAULT, 41690792Sgshapiro "Memory dump unavailable.\r\n"); 41790792Sgshapiro (void) sm_io_fprintf(s, SM_TIME_DEFAULT, 41890792Sgshapiro "To fix, rebuild with -DSM_HEAP_CHECK\r\n"); 41990792Sgshapiro# endif /* SM_HEAP_CHECK */ 42090792Sgshapiro break; 42190792Sgshapiro 42242575Speter case CMDERROR: /* unknown command */ 42390792Sgshapiro (void) sm_io_fprintf(s, SM_TIME_DEFAULT, 42490792Sgshapiro "Bad command (%s)\r\n", cmdbuf); 42542575Speter break; 42642575Speter } 42790792Sgshapiro (void) sm_io_close(s, SM_TIME_DEFAULT); 42864562Sgshapiro if (ev != NULL) 42990792Sgshapiro sm_clrevent(ev); 43064562Sgshapiro exit(exitstat); 43142575Speter} 432