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