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