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