Initial import from FreeBSD RELENG_4:
[dragonfly.git] / contrib / sendmail / src / control.c
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 8.118.4.3 2002/11/14 00:15: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 #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 }