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