1/*
2 * Copyright (c) 1998-2004, 2006 Proofpoint, Inc. and its suppliers.
3 *	All rights reserved.
4 *
5 * By using this file, you agree to the terms and conditions set
6 * forth in the LICENSE file which can be found at the top level of
7 * the sendmail distribution.
8 *
9 */
10
11#include <sendmail.h>
12
13SM_RCSID("@(#)$Id: control.c,v 8.130 2013-11-22 20:51:55 ca Exp $")
14
15#include <sm/fdset.h>
16
17/* values for cmd_code */
18#define CMDERROR	0	/* bad command */
19#define CMDRESTART	1	/* restart daemon */
20#define CMDSHUTDOWN	2	/* end daemon */
21#define CMDHELP		3	/* help */
22#define CMDSTATUS	4	/* daemon status */
23#define CMDMEMDUMP	5	/* dump memory, to find memory leaks */
24#define CMDMSTAT	6	/* daemon status, more info, tagged data */
25
26struct cmd
27{
28	char	*cmd_name;	/* command name */
29	int	cmd_code;	/* internal code, see below */
30};
31
32static struct cmd	CmdTab[] =
33{
34	{ "help",	CMDHELP		},
35	{ "restart",	CMDRESTART	},
36	{ "shutdown",	CMDSHUTDOWN	},
37	{ "status",	CMDSTATUS	},
38	{ "memdump",	CMDMEMDUMP	},
39	{ "mstat",	CMDMSTAT	},
40	{ NULL,		CMDERROR	}
41};
42
43
44
45static void	controltimeout __P((int));
46int ControlSocket = -1;
47
48/*
49**  OPENCONTROLSOCKET -- create/open the daemon control named socket
50**
51**	Creates and opens a named socket for external control over
52**	the sendmail daemon.
53**
54**	Parameters:
55**		none.
56**
57**	Returns:
58**		0 if successful, -1 otherwise
59*/
60
61int
62opencontrolsocket()
63{
64# if NETUNIX
65	int save_errno;
66	int rval;
67	long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN;
68	struct sockaddr_un controladdr;
69
70	if (ControlSocketName == NULL || *ControlSocketName == '\0')
71		return 0;
72
73	if (strlen(ControlSocketName) >= sizeof(controladdr.sun_path))
74	{
75		errno = ENAMETOOLONG;
76		return -1;
77	}
78
79	rval = safefile(ControlSocketName, RunAsUid, RunAsGid, RunAsUserName,
80			sff, S_IRUSR|S_IWUSR, NULL);
81
82	/* if not safe, don't create */
83	if (rval != 0)
84	{
85		errno = rval;
86		return -1;
87	}
88
89	ControlSocket = socket(AF_UNIX, SOCK_STREAM, 0);
90	if (ControlSocket < 0)
91		return -1;
92	if (SM_FD_SETSIZE > 0 && ControlSocket >= SM_FD_SETSIZE)
93	{
94		clrcontrol();
95		errno = EINVAL;
96		return -1;
97	}
98
99	(void) unlink(ControlSocketName);
100	memset(&controladdr, '\0', sizeof(controladdr));
101	controladdr.sun_family = AF_UNIX;
102	(void) sm_strlcpy(controladdr.sun_path, ControlSocketName,
103			  sizeof(controladdr.sun_path));
104
105	if (bind(ControlSocket, (struct sockaddr *) &controladdr,
106		 sizeof(controladdr)) < 0)
107	{
108		save_errno = errno;
109		clrcontrol();
110		errno = save_errno;
111		return -1;
112	}
113
114	if (geteuid() == 0)
115	{
116		uid_t u = 0;
117
118		if (RunAsUid != 0)
119			u = RunAsUid;
120		else if (TrustedUid != 0)
121			u = TrustedUid;
122
123		if (u != 0 &&
124		    chown(ControlSocketName, u, -1) < 0)
125		{
126			save_errno = errno;
127			sm_syslog(LOG_ALERT, NOQID,
128				  "ownership change on %s to uid %d failed: %s",
129				  ControlSocketName, (int) u,
130				  sm_errstring(save_errno));
131			message("050 ownership change on %s to uid %d failed: %s",
132				ControlSocketName, (int) u,
133				sm_errstring(save_errno));
134			closecontrolsocket(true);
135			errno = save_errno;
136			return -1;
137		}
138	}
139
140	if (chmod(ControlSocketName, S_IRUSR|S_IWUSR) < 0)
141	{
142		save_errno = errno;
143		closecontrolsocket(true);
144		errno = save_errno;
145		return -1;
146	}
147
148	if (listen(ControlSocket, 8) < 0)
149	{
150		save_errno = errno;
151		closecontrolsocket(true);
152		errno = save_errno;
153		return -1;
154	}
155# endif /* NETUNIX */
156	return 0;
157}
158/*
159**  CLOSECONTROLSOCKET -- close the daemon control named socket
160**
161**	Close a named socket.
162**
163**	Parameters:
164**		fullclose -- if set, close the socket and remove it;
165**			     otherwise, just remove it
166**
167**	Returns:
168**		none.
169*/
170
171void
172closecontrolsocket(fullclose)
173	bool fullclose;
174{
175# if NETUNIX
176	long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN;
177
178	if (ControlSocket >= 0)
179	{
180		int rval;
181
182		if (fullclose)
183		{
184			(void) close(ControlSocket);
185			ControlSocket = -1;
186		}
187
188		rval = safefile(ControlSocketName, RunAsUid, RunAsGid,
189				RunAsUserName, sff, S_IRUSR|S_IWUSR, NULL);
190
191		/* if not safe, don't unlink */
192		if (rval != 0)
193			return;
194
195		if (unlink(ControlSocketName) < 0)
196		{
197			sm_syslog(LOG_WARNING, NOQID,
198				  "Could not remove control socket: %s",
199				  sm_errstring(errno));
200			return;
201		}
202	}
203# endif /* NETUNIX */
204	return;
205}
206/*
207**  CLRCONTROL -- reset the control connection
208**
209**	Parameters:
210**		none.
211**
212**	Returns:
213**		none.
214**
215**	Side Effects:
216**		releases any resources used by the control interface.
217*/
218
219void
220clrcontrol()
221{
222# if NETUNIX
223	if (ControlSocket >= 0)
224		(void) close(ControlSocket);
225	ControlSocket = -1;
226# endif /* NETUNIX */
227}
228/*
229**  CONTROL_COMMAND -- read and process command from named socket
230**
231**	Read and process the command from the opened socket.
232**	Exits when done since it is running in a forked child.
233**
234**	Parameters:
235**		sock -- the opened socket from getrequests()
236**		e -- the current envelope
237**
238**	Returns:
239**		none.
240*/
241
242static jmp_buf	CtxControlTimeout;
243
244/* ARGSUSED0 */
245static void
246controltimeout(timeout)
247	int timeout;
248{
249	/*
250	**  NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
251	**	ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
252	**	DOING.
253	*/
254
255	errno = ETIMEDOUT;
256	longjmp(CtxControlTimeout, 1);
257}
258
259void
260control_command(sock, e)
261	int sock;
262	ENVELOPE *e;
263{
264	volatile int exitstat = EX_OK;
265	SM_FILE_T *s = NULL;
266	SM_EVENT *ev = NULL;
267	SM_FILE_T *traffic;
268	SM_FILE_T *oldout;
269	char *cmd;
270	char *p;
271	struct cmd *c;
272	char cmdbuf[MAXLINE];
273	char inp[MAXLINE];
274
275	sm_setproctitle(false, e, "control cmd read");
276
277	if (TimeOuts.to_control > 0)
278	{
279		/* handle possible input timeout */
280		if (setjmp(CtxControlTimeout) != 0)
281		{
282			if (LogLevel > 2)
283				sm_syslog(LOG_NOTICE, e->e_id,
284					  "timeout waiting for input during control command");
285			exit(EX_IOERR);
286		}
287		ev = sm_setevent(TimeOuts.to_control, controltimeout,
288				 TimeOuts.to_control);
289	}
290
291	s = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, (void *) &sock,
292		       SM_IO_RDWR, NULL);
293	if (s == NULL)
294	{
295		int save_errno = errno;
296
297		(void) close(sock);
298		errno = save_errno;
299		exit(EX_IOERR);
300	}
301	(void) sm_io_setvbuf(s, SM_TIME_DEFAULT, NULL,
302			     SM_IO_NBF, SM_IO_BUFSIZ);
303
304	if (sm_io_fgets(s, SM_TIME_DEFAULT, inp, sizeof(inp)) < 0)
305	{
306		(void) sm_io_close(s, SM_TIME_DEFAULT);
307		exit(EX_IOERR);
308	}
309	(void) sm_io_flush(s, SM_TIME_DEFAULT);
310
311	/* clean up end of line */
312	fixcrlf(inp, true);
313
314	sm_setproctitle(false, e, "control: %s", inp);
315
316	/* break off command */
317	for (p = inp; SM_ISSPACE(*p); p++)
318		continue;
319	cmd = cmdbuf;
320	while (*p != '\0' &&
321	       !(SM_ISSPACE(*p)) && cmd < &cmdbuf[sizeof(cmdbuf) - 2])
322		*cmd++ = *p++;
323	*cmd = '\0';
324
325	/* throw away leading whitespace */
326	while (SM_ISSPACE(*p))
327		p++;
328
329	/* decode command */
330	for (c = CmdTab; c->cmd_name != NULL; c++)
331	{
332		if (sm_strcasecmp(c->cmd_name, cmdbuf) == 0)
333			break;
334	}
335
336	switch (c->cmd_code)
337	{
338	  case CMDHELP:		/* get help */
339		traffic = TrafficLogFile;
340		TrafficLogFile = NULL;
341		oldout = OutChannel;
342		OutChannel = s;
343		help("control", e);
344		TrafficLogFile = traffic;
345		OutChannel = oldout;
346		break;
347
348	  case CMDRESTART:	/* restart the daemon */
349		(void) sm_io_fprintf(s, SM_TIME_DEFAULT, "OK\r\n");
350		exitstat = EX_RESTART;
351		break;
352
353	  case CMDSHUTDOWN:	/* kill the daemon */
354		(void) sm_io_fprintf(s, SM_TIME_DEFAULT, "OK\r\n");
355		exitstat = EX_SHUTDOWN;
356		break;
357
358	  case CMDSTATUS:	/* daemon status */
359		proc_list_probe();
360		{
361			int qgrp;
362			long bsize;
363			long free;
364
365			/* XXX need to deal with different partitions */
366			qgrp = e->e_qgrp;
367			if (!ISVALIDQGRP(qgrp))
368				qgrp = 0;
369			free = freediskspace(Queue[qgrp]->qg_qdir, &bsize);
370
371			/*
372			**  Prevent overflow and don't lose
373			**  precision (if bsize == 512)
374			*/
375
376			if (free > 0)
377				free = (long)((double) free *
378					      ((double) bsize / 1024));
379
380			(void) sm_io_fprintf(s, SM_TIME_DEFAULT,
381					     "%d/%d/%ld/%d\r\n",
382					     CurChildren, MaxChildren,
383					     free, getla());
384		}
385		proc_list_display(s, "");
386		break;
387
388	  case CMDMSTAT:	/* daemon status, extended, tagged format */
389		proc_list_probe();
390		(void) sm_io_fprintf(s, SM_TIME_DEFAULT,
391				     "C:%d\r\nM:%d\r\nL:%d\r\n",
392				     CurChildren, MaxChildren,
393				     getla());
394		printnqe(s, "Q:");
395		disk_status(s, "D:");
396		proc_list_display(s, "P:");
397		break;
398
399	  case CMDMEMDUMP:	/* daemon memory dump, to find memory leaks */
400# if SM_HEAP_CHECK
401		/* dump the heap, if we are checking for memory leaks */
402		if (sm_debug_active(&SmHeapCheck, 2))
403		{
404			sm_heap_report(s, sm_debug_level(&SmHeapCheck) - 1);
405		}
406		else
407		{
408			(void) sm_io_fprintf(s, SM_TIME_DEFAULT,
409					     "Memory dump unavailable.\r\n");
410			(void) sm_io_fprintf(s, SM_TIME_DEFAULT,
411					     "To fix, run sendmail with -dsm_check_heap.4\r\n");
412		}
413# else /* SM_HEAP_CHECK */
414		(void) sm_io_fprintf(s, SM_TIME_DEFAULT,
415				     "Memory dump unavailable.\r\n");
416		(void) sm_io_fprintf(s, SM_TIME_DEFAULT,
417				     "To fix, rebuild with -DSM_HEAP_CHECK\r\n");
418# endif /* SM_HEAP_CHECK */
419		break;
420
421	  case CMDERROR:	/* unknown command */
422		(void) sm_io_fprintf(s, SM_TIME_DEFAULT,
423				     "Bad command (%s)\r\n", cmdbuf);
424		break;
425	}
426	(void) sm_io_close(s, SM_TIME_DEFAULT);
427	if (ev != NULL)
428		sm_clrevent(ev);
429	exit(exitstat);
430}
431