1 /* 2 * Copyright (c) 1998-2003 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.8 2003/06/24 17:45:27 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 } 437