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