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