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