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