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