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