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