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