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 13SM_RCSID("@(#)$Id: control.c,v 8.129 2013/03/12 15:24:52 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 26struct cmd 27{ 28 char *cmd_name; /* command name */ 29 int cmd_code; /* internal code, see below */ 30}; 31 32static 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 45static void controltimeout __P((int)); 46int 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 61int 62opencontrolsocket() 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 171void 172closecontrolsocket(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 219void 220clrcontrol() 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 242static jmp_buf CtxControlTimeout; 243 244/* ARGSUSED0 */ 245static void 246controltimeout(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 259void 260control_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)) < 0) 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} 432