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