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