1 /* 2 * Copyright (c) 1998-2000 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 #ifndef lint 12 static char id[] = "@(#)$Id: control.c,v 8.44.14.8 2000/09/17 17:04:26 gshapiro Exp $"; 13 #endif /* ! lint */ 14 15 #include <sendmail.h> 16 17 18 int ControlSocket = -1; 19 20 /* 21 ** OPENCONTROLSOCKET -- create/open the daemon control named socket 22 ** 23 ** Creates and opens a named socket for external control over 24 ** the sendmail daemon. 25 ** 26 ** Parameters: 27 ** none. 28 ** 29 ** Returns: 30 ** 0 if successful, -1 otherwise 31 */ 32 33 int 34 opencontrolsocket() 35 { 36 #if NETUNIX 37 int save_errno; 38 int rval; 39 long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN; 40 struct sockaddr_un controladdr; 41 42 if (ControlSocketName == NULL) 43 return 0; 44 45 if (strlen(ControlSocketName) >= sizeof controladdr.sun_path) 46 { 47 errno = ENAMETOOLONG; 48 return -1; 49 } 50 51 rval = safefile(ControlSocketName, RunAsUid, RunAsGid, RunAsUserName, 52 sff, S_IRUSR|S_IWUSR, NULL); 53 54 /* if not safe, don't create */ 55 if (rval != 0) 56 { 57 errno = rval; 58 return -1; 59 } 60 61 ControlSocket = socket(AF_UNIX, SOCK_STREAM, 0); 62 if (ControlSocket < 0) 63 return -1; 64 65 (void) unlink(ControlSocketName); 66 memset(&controladdr, '\0', sizeof controladdr); 67 controladdr.sun_family = AF_UNIX; 68 (void) strlcpy(controladdr.sun_path, ControlSocketName, 69 sizeof controladdr.sun_path); 70 71 if (bind(ControlSocket, (struct sockaddr *) &controladdr, 72 sizeof controladdr) < 0) 73 { 74 save_errno = errno; 75 clrcontrol(); 76 errno = save_errno; 77 return -1; 78 } 79 80 if (geteuid() == 0 && TrustedUid != 0) 81 { 82 if (chown(ControlSocketName, TrustedUid, -1) < 0) 83 { 84 save_errno = errno; 85 sm_syslog(LOG_ALERT, NOQID, 86 "ownership change on %s failed: %s", 87 ControlSocketName, errstring(save_errno)); 88 message("050 ownership change on %s failed: %s", 89 ControlSocketName, errstring(save_errno)); 90 closecontrolsocket(TRUE); 91 errno = save_errno; 92 return -1; 93 } 94 } 95 96 if (chmod(ControlSocketName, S_IRUSR|S_IWUSR) < 0) 97 { 98 save_errno = errno; 99 closecontrolsocket(TRUE); 100 errno = save_errno; 101 return -1; 102 } 103 104 if (listen(ControlSocket, 8) < 0) 105 { 106 save_errno = errno; 107 closecontrolsocket(TRUE); 108 errno = save_errno; 109 return -1; 110 } 111 #endif /* NETUNIX */ 112 return 0; 113 } 114 /* 115 ** CLOSECONTROLSOCKET -- close the daemon control named socket 116 ** 117 ** Close a named socket. 118 ** 119 ** Parameters: 120 ** fullclose -- if set, close the socket and remove it; 121 ** otherwise, just remove it 122 ** 123 ** Returns: 124 ** none. 125 */ 126 127 void 128 closecontrolsocket(fullclose) 129 bool fullclose; 130 { 131 #if NETUNIX 132 long sff = SFF_SAFEDIRPATH|SFF_OPENASROOT|SFF_NOLINK|SFF_CREAT|SFF_MUSTOWN; 133 134 if (ControlSocket >= 0) 135 { 136 int rval; 137 138 if (fullclose) 139 { 140 (void) close(ControlSocket); 141 ControlSocket = -1; 142 } 143 144 rval = safefile(ControlSocketName, RunAsUid, RunAsGid, RunAsUserName, 145 sff, S_IRUSR|S_IWUSR, NULL); 146 147 /* if not safe, don't unlink */ 148 if (rval != 0) 149 return; 150 151 if (unlink(ControlSocketName) < 0) 152 { 153 sm_syslog(LOG_WARNING, NOQID, 154 "Could not remove control socket: %s", 155 errstring(errno)); 156 return; 157 } 158 } 159 #endif /* NETUNIX */ 160 return; 161 } 162 /* 163 ** CLRCONTROL -- reset the control connection 164 ** 165 ** Parameters: 166 ** none. 167 ** 168 ** Returns: 169 ** none. 170 ** 171 ** Side Effects: 172 ** releases any resources used by the control interface. 173 */ 174 175 void 176 clrcontrol() 177 { 178 #if NETUNIX 179 if (ControlSocket >= 0) 180 (void) close(ControlSocket); 181 ControlSocket = -1; 182 #endif /* NETUNIX */ 183 } 184 185 #ifndef NOT_SENDMAIL 186 187 /* 188 ** CONTROL_COMMAND -- read and process command from named socket 189 ** 190 ** Read and process the command from the opened socket. 191 ** Exits when done since it is running in a forked child. 192 ** 193 ** Parameters: 194 ** sock -- the opened socket from getrequests() 195 ** e -- the current envelope 196 ** 197 ** Returns: 198 ** none. 199 */ 200 201 struct cmd 202 { 203 char *cmd_name; /* command name */ 204 int cmd_code; /* internal code, see below */ 205 }; 206 207 /* values for cmd_code */ 208 # define CMDERROR 0 /* bad command */ 209 # define CMDRESTART 1 /* restart daemon */ 210 # define CMDSHUTDOWN 2 /* end daemon */ 211 # define CMDHELP 3 /* help */ 212 # define CMDSTATUS 4 /* daemon status */ 213 214 static struct cmd CmdTab[] = 215 { 216 { "help", CMDHELP }, 217 { "restart", CMDRESTART }, 218 { "shutdown", CMDSHUTDOWN }, 219 { "status", CMDSTATUS }, 220 { NULL, CMDERROR } 221 }; 222 223 static jmp_buf CtxControlTimeout; 224 225 static void 226 controltimeout(timeout) 227 time_t timeout; 228 { 229 longjmp(CtxControlTimeout, 1); 230 } 231 232 void 233 control_command(sock, e) 234 int sock; 235 ENVELOPE *e; 236 { 237 volatile int exitstat = EX_OK; 238 FILE *s = NULL; 239 EVENT *ev = NULL; 240 FILE *traffic; 241 FILE *oldout; 242 char *cmd; 243 char *p; 244 struct cmd *c; 245 char cmdbuf[MAXLINE]; 246 char inp[MAXLINE]; 247 248 sm_setproctitle(FALSE, e, "control cmd read"); 249 250 if (TimeOuts.to_control > 0) 251 { 252 /* handle possible input timeout */ 253 if (setjmp(CtxControlTimeout) != 0) 254 { 255 if (LogLevel > 2) 256 sm_syslog(LOG_NOTICE, e->e_id, 257 "timeout waiting for input during control command"); 258 exit(EX_IOERR); 259 } 260 ev = setevent(TimeOuts.to_control, controltimeout, 261 TimeOuts.to_control); 262 } 263 264 s = fdopen(sock, "r+"); 265 if (s == NULL) 266 { 267 int save_errno = errno; 268 269 (void) close(sock); 270 errno = save_errno; 271 exit(EX_IOERR); 272 } 273 setbuf(s, NULL); 274 275 if (fgets(inp, sizeof inp, s) == NULL) 276 { 277 (void) fclose(s); 278 exit(EX_IOERR); 279 } 280 (void) fflush(s); 281 282 /* clean up end of line */ 283 fixcrlf(inp, TRUE); 284 285 sm_setproctitle(FALSE, e, "control: %s", inp); 286 287 /* break off command */ 288 for (p = inp; isascii(*p) && isspace(*p); p++) 289 continue; 290 cmd = cmdbuf; 291 while (*p != '\0' && 292 !(isascii(*p) && isspace(*p)) && 293 cmd < &cmdbuf[sizeof cmdbuf - 2]) 294 *cmd++ = *p++; 295 *cmd = '\0'; 296 297 /* throw away leading whitespace */ 298 while (isascii(*p) && isspace(*p)) 299 p++; 300 301 /* decode command */ 302 for (c = CmdTab; c->cmd_name != NULL; c++) 303 { 304 if (strcasecmp(c->cmd_name, cmdbuf) == 0) 305 break; 306 } 307 308 switch (c->cmd_code) 309 { 310 case CMDHELP: /* get help */ 311 traffic = TrafficLogFile; 312 TrafficLogFile = NULL; 313 oldout = OutChannel; 314 OutChannel = s; 315 help("control", e); 316 TrafficLogFile = traffic; 317 OutChannel = oldout; 318 break; 319 320 case CMDRESTART: /* restart the daemon */ 321 fprintf(s, "OK\r\n"); 322 exitstat = EX_RESTART; 323 break; 324 325 case CMDSHUTDOWN: /* kill the daemon */ 326 fprintf(s, "OK\r\n"); 327 exitstat = EX_SHUTDOWN; 328 break; 329 330 case CMDSTATUS: /* daemon status */ 331 proc_list_probe(); 332 { 333 long bsize; 334 long free; 335 336 free = freediskspace(QueueDir, &bsize); 337 338 /* 339 ** Prevent overflow and don't lose 340 ** precision (if bsize == 512) 341 */ 342 343 free = (long)((double)free * ((double)bsize / 1024)); 344 345 fprintf(s, "%d/%d/%ld/%d\r\n", 346 CurChildren, MaxChildren, 347 free, sm_getla(NULL)); 348 } 349 proc_list_display(s); 350 break; 351 352 case CMDERROR: /* unknown command */ 353 fprintf(s, "Bad command (%s)\r\n", cmdbuf); 354 break; 355 } 356 (void) fclose(s); 357 if (ev != NULL) 358 clrevent(ev); 359 exit(exitstat); 360 } 361 #endif /* ! NOT_SENDMAIL */ 362 363