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