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