xref: /freebsd/contrib/sendmail/src/control.c (revision 6990ffd8a95caaba6858ad44ff1b3157d1efba8f)
1 /*
2  * Copyright (c) 1998-2001 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 #ifndef lint
12 static char id[] = "@(#)$Id: control.c,v 8.44.14.20 2001/05/03 17:24:03 gshapiro Exp $";
13 #endif /* ! lint */
14 
15 #include <sendmail.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 
24 struct cmd
25 {
26 	char	*cmd_name;	/* command name */
27 	int	cmd_code;	/* internal code, see below */
28 };
29 
30 static struct cmd	CmdTab[] =
31 {
32 	{ "help",	CMDHELP		},
33 	{ "restart",	CMDRESTART	},
34 	{ "shutdown",	CMDSHUTDOWN	},
35 	{ "status",	CMDSTATUS	},
36 	{ NULL,		CMDERROR	}
37 };
38 
39 
40 int ControlSocket = -1;
41 
42 /*
43 **  OPENCONTROLSOCKET -- create/open the daemon control named socket
44 **
45 **	Creates and opens a named socket for external control over
46 **	the sendmail daemon.
47 **
48 **	Parameters:
49 **		none.
50 **
51 **	Returns:
52 **		0 if successful, -1 otherwise
53 */
54 
55 int
56 opencontrolsocket()
57 {
58 #if NETUNIX
59 	int save_errno;
60 	int rval;
61 	long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN;
62 	struct sockaddr_un controladdr;
63 
64 	if (ControlSocketName == NULL)
65 		return 0;
66 
67 	if (strlen(ControlSocketName) >= sizeof controladdr.sun_path)
68 	{
69 		errno = ENAMETOOLONG;
70 		return -1;
71 	}
72 
73 	rval = safefile(ControlSocketName, RunAsUid, RunAsGid, RunAsUserName,
74 			sff, S_IRUSR|S_IWUSR, NULL);
75 
76 	/* if not safe, don't create */
77 	if (rval != 0)
78 	{
79 		errno = rval;
80 		return -1;
81 	}
82 
83 	ControlSocket = socket(AF_UNIX, SOCK_STREAM, 0);
84 	if (ControlSocket < 0)
85 		return -1;
86 
87 	(void) unlink(ControlSocketName);
88 	memset(&controladdr, '\0', sizeof controladdr);
89 	controladdr.sun_family = AF_UNIX;
90 	(void) strlcpy(controladdr.sun_path, ControlSocketName,
91 		       sizeof controladdr.sun_path);
92 
93 	if (bind(ControlSocket, (struct sockaddr *) &controladdr,
94 		 sizeof controladdr) < 0)
95 	{
96 		save_errno = errno;
97 		clrcontrol();
98 		errno = save_errno;
99 		return -1;
100 	}
101 
102 	if (geteuid() == 0)
103 	{
104 		uid_t u = 0;
105 
106 		if (RunAsUid != 0)
107 			u = RunAsUid;
108 		else if (TrustedUid != 0)
109 			u = TrustedUid;
110 
111 		if (u != 0 &&
112 		    chown(ControlSocketName, u, -1) < 0)
113 		{
114 			save_errno = errno;
115 			sm_syslog(LOG_ALERT, NOQID,
116 				  "ownership change on %s to uid %d failed: %s",
117 				  ControlSocketName, (int) u,
118 				  errstring(save_errno));
119 			message("050 ownership change on %s to uid %d failed: %s",
120 				ControlSocketName, (int) u,
121 				errstring(save_errno));
122 			closecontrolsocket(TRUE);
123 			errno = save_errno;
124 			return -1;
125 		}
126 	}
127 
128 	if (chmod(ControlSocketName, S_IRUSR|S_IWUSR) < 0)
129 	{
130 		save_errno = errno;
131 		closecontrolsocket(TRUE);
132 		errno = save_errno;
133 		return -1;
134 	}
135 
136 	if (listen(ControlSocket, 8) < 0)
137 	{
138 		save_errno = errno;
139 		closecontrolsocket(TRUE);
140 		errno = save_errno;
141 		return -1;
142 	}
143 #endif /* NETUNIX */
144 	return 0;
145 }
146 /*
147 **  CLOSECONTROLSOCKET -- close the daemon control named socket
148 **
149 **	Close a named socket.
150 **
151 **	Parameters:
152 **		fullclose -- if set, close the socket and remove it;
153 **			     otherwise, just remove it
154 **
155 **	Returns:
156 **		none.
157 */
158 
159 void
160 closecontrolsocket(fullclose)
161 	bool fullclose;
162 {
163 #if NETUNIX
164 	long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN;
165 
166 	if (ControlSocket >= 0)
167 	{
168 		int rval;
169 
170 		if (fullclose)
171 		{
172 			(void) close(ControlSocket);
173 			ControlSocket = -1;
174 		}
175 
176 		rval = safefile(ControlSocketName, RunAsUid, RunAsGid,
177 				RunAsUserName, sff, S_IRUSR|S_IWUSR, NULL);
178 
179 		/* if not safe, don't unlink */
180 		if (rval != 0)
181 			return;
182 
183 		if (unlink(ControlSocketName) < 0)
184 		{
185 			sm_syslog(LOG_WARNING, NOQID,
186 				  "Could not remove control socket: %s",
187 				  errstring(errno));
188 			return;
189 		}
190 	}
191 #endif /* NETUNIX */
192 	return;
193 }
194 /*
195 **  CLRCONTROL -- reset the control connection
196 **
197 **	Parameters:
198 **		none.
199 **
200 **	Returns:
201 **		none.
202 **
203 **	Side Effects:
204 **		releases any resources used by the control interface.
205 */
206 
207 void
208 clrcontrol()
209 {
210 #if NETUNIX
211 	if (ControlSocket >= 0)
212 		(void) close(ControlSocket);
213 	ControlSocket = -1;
214 #endif /* NETUNIX */
215 }
216 
217 #ifndef NOT_SENDMAIL
218 
219 /*
220 **  CONTROL_COMMAND -- read and process command from named socket
221 **
222 **	Read and process the command from the opened socket.
223 **	Exits when done since it is running in a forked child.
224 **
225 **	Parameters:
226 **		sock -- the opened socket from getrequests()
227 **		e -- the current envelope
228 **
229 **	Returns:
230 **		none.
231 */
232 
233 static jmp_buf	CtxControlTimeout;
234 
235 static void
236 controltimeout(timeout)
237 	time_t timeout;
238 {
239 	/*
240 	**  NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
241 	**	ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
242 	**	DOING.
243 	*/
244 
245 	errno = ETIMEDOUT;
246 	longjmp(CtxControlTimeout, 1);
247 }
248 
249 void
250 control_command(sock, e)
251 	int sock;
252 	ENVELOPE *e;
253 {
254 	volatile int exitstat = EX_OK;
255 	FILE *s = NULL;
256 	EVENT *ev = NULL;
257 	FILE *traffic;
258 	FILE *oldout;
259 	char *cmd;
260 	char *p;
261 	struct cmd *c;
262 	char cmdbuf[MAXLINE];
263 	char inp[MAXLINE];
264 
265 	sm_setproctitle(FALSE, e, "control cmd read");
266 
267 	if (TimeOuts.to_control > 0)
268 	{
269 		/* handle possible input timeout */
270 		if (setjmp(CtxControlTimeout) != 0)
271 		{
272 			if (LogLevel > 2)
273 				sm_syslog(LOG_NOTICE, e->e_id,
274 					  "timeout waiting for input during control command");
275 			exit(EX_IOERR);
276 		}
277 		ev = setevent(TimeOuts.to_control, controltimeout,
278 			      TimeOuts.to_control);
279 	}
280 
281 	s = fdopen(sock, "r+");
282 	if (s == NULL)
283 	{
284 		int save_errno = errno;
285 
286 		(void) close(sock);
287 		errno = save_errno;
288 		exit(EX_IOERR);
289 	}
290 	setbuf(s, NULL);
291 
292 	if (fgets(inp, sizeof inp, s) == NULL)
293 	{
294 		(void) fclose(s);
295 		exit(EX_IOERR);
296 	}
297 	(void) fflush(s);
298 
299 	/* clean up end of line */
300 	fixcrlf(inp, TRUE);
301 
302 	sm_setproctitle(FALSE, e, "control: %s", inp);
303 
304 	/* break off command */
305 	for (p = inp; isascii(*p) && isspace(*p); p++)
306 		continue;
307 	cmd = cmdbuf;
308 	while (*p != '\0' &&
309 	       !(isascii(*p) && isspace(*p)) &&
310 	       cmd < &cmdbuf[sizeof cmdbuf - 2])
311 		*cmd++ = *p++;
312 	*cmd = '\0';
313 
314 	/* throw away leading whitespace */
315 	while (isascii(*p) && isspace(*p))
316 		p++;
317 
318 	/* decode command */
319 	for (c = CmdTab; c->cmd_name != NULL; c++)
320 	{
321 		if (strcasecmp(c->cmd_name, cmdbuf) == 0)
322 			break;
323 	}
324 
325 	switch (c->cmd_code)
326 	{
327 	  case CMDHELP:		/* get help */
328 		traffic = TrafficLogFile;
329 		TrafficLogFile = NULL;
330 		oldout = OutChannel;
331 		OutChannel = s;
332 		help("control", e);
333 		TrafficLogFile = traffic;
334 		OutChannel = oldout;
335 		break;
336 
337 	  case CMDRESTART:	/* restart the daemon */
338 		fprintf(s, "OK\r\n");
339 		exitstat = EX_RESTART;
340 		break;
341 
342 	  case CMDSHUTDOWN:	/* kill the daemon */
343 		fprintf(s, "OK\r\n");
344 		exitstat = EX_SHUTDOWN;
345 		break;
346 
347 	  case CMDSTATUS:	/* daemon status */
348 		proc_list_probe();
349 		{
350 			long bsize;
351 			long free;
352 
353 			free = freediskspace(QueueDir, &bsize);
354 
355 			/*
356 			**  Prevent overflow and don't lose
357 			**  precision (if bsize == 512)
358 			*/
359 
360 			free = (long)((double)free * ((double)bsize / 1024));
361 
362 			fprintf(s, "%d/%d/%ld/%d\r\n",
363 				CurChildren, MaxChildren,
364 				free, sm_getla(NULL));
365 		}
366 		proc_list_display(s);
367 		break;
368 
369 	  case CMDERROR:	/* unknown command */
370 		fprintf(s, "Bad command (%s)\r\n", cmdbuf);
371 		break;
372 	}
373 	(void) fclose(s);
374 	if (ev != NULL)
375 		clrevent(ev);
376 	exit(exitstat);
377 }
378 #endif /* ! NOT_SENDMAIL */
379 
380