xref: /freebsd/contrib/sendmail/src/control.c (revision f7c32ed617858bcd22f8d1b03199099d50125721)
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/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 
26 struct cmd
27 {
28 	char	*cmd_name;	/* command name */
29 	int	cmd_code;	/* internal code, see below */
30 };
31 
32 static 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 
45 static void	controltimeout __P((int));
46 int 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 
61 int
62 opencontrolsocket()
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 
171 void
172 closecontrolsocket(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 
219 void
220 clrcontrol()
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 
242 static jmp_buf	CtxControlTimeout;
243 
244 /* ARGSUSED0 */
245 static void
246 controltimeout(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 
259 void
260 control_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