1#!/usr/bin/perl -w 2 3# $Id: smcontrol.pl,v 8.8 2008-07-21 21:31:43 ca Exp $ 4 5use strict; 6use Getopt::Std; 7use FileHandle; 8use Socket; 9 10my $sendmailDaemon = "/usr/sbin/sendmail -q30m -bd"; 11 12########################################################################## 13# 14# &get_controlname -- read ControlSocketName option from sendmail.cf 15# 16# Parameters: 17# none. 18# 19# Returns: 20# control socket filename, undef if not found 21# 22 23sub get_controlname 24{ 25 my $cn = undef; 26 my $qd = undef; 27 28 open(CF, "</etc/mail/sendmail.cf") or return $cn; 29 while (<CF>) 30 { 31 chomp; 32 if (/^O ControlSocketName\s*=\s*([^#]+)$/o) 33 { 34 $cn = $1; 35 } 36 if (/^O QueueDirectory\s*=\s*([^#]+)$/o) 37 { 38 $qd = $1; 39 } 40 if (/^OQ([^#]+)$/o) 41 { 42 $qd = $1; 43 } 44 } 45 close(CF); 46 if (not defined $cn) 47 { 48 return undef; 49 } 50 if ($cn !~ /^\//o) 51 { 52 return undef if (not defined $qd); 53 54 $cn = $qd . "/" . $cn; 55 } 56 return $cn; 57} 58 59########################################################################## 60# 61# &do_command -- send command to sendmail daemon view control socket 62# 63# Parameters: 64# controlsocket -- filename for socket 65# command -- command to send 66# 67# Returns: 68# reply from sendmail daemon 69# 70 71sub do_command 72{ 73 my $controlsocket = shift; 74 my $command = shift; 75 my $proto = getprotobyname('ip'); 76 my @reply; 77 my $i; 78 79 socket(SOCK, PF_UNIX, SOCK_STREAM, $proto) or return undef; 80 81 for ($i = 0; $i < 4; $i++) 82 { 83 if (!connect(SOCK, sockaddr_un($controlsocket))) 84 { 85 if ($i == 3) 86 { 87 close(SOCK); 88 return undef; 89 } 90 sleep 1; 91 next; 92 } 93 last; 94 } 95 autoflush SOCK 1; 96 print SOCK "$command\n"; 97 @reply = <SOCK>; 98 close(SOCK); 99 return join '', @reply; 100} 101 102########################################################################## 103# 104# &sendmail_running -- check if sendmail is running via SMTP 105# 106# Parameters: 107# none 108# 109# Returns: 110# 1 if running, undef otherwise 111# 112 113sub sendmail_running 114{ 115 my $port = getservbyname("smtp", "tcp") || 25; 116 my $proto = getprotobyname("tcp"); 117 my $iaddr = inet_aton("localhost"); 118 my $paddr = sockaddr_in($port, $iaddr); 119 120 socket(SOCK, PF_INET, SOCK_STREAM, $proto) or return undef; 121 if (!connect(SOCK, $paddr)) 122 { 123 close(SOCK); 124 return undef; 125 } 126 autoflush SOCK 1; 127 while (<SOCK>) 128 { 129 if (/^(\d{3})([ -])/) 130 { 131 if ($1 != 220) 132 { 133 close(SOCK); 134 return undef; 135 } 136 } 137 else 138 { 139 close(SOCK); 140 return undef; 141 } 142 last if ($2 eq " "); 143 } 144 print SOCK "QUIT\n"; 145 while (<SOCK>) 146 { 147 last if (/^\d{3} /); 148 } 149 close(SOCK); 150 return 1; 151} 152 153########################################################################## 154# 155# &munge_status -- turn machine readable status into human readable text 156# 157# Parameters: 158# raw -- raw results from sendmail daemon STATUS query 159# 160# Returns: 161# human readable text 162# 163 164sub munge_status 165{ 166 my $raw = shift; 167 my $cooked = ""; 168 my $daemonStatus = ""; 169 170 if ($raw =~ /^(\d+)\/(\d+)\/(\d+)\/(\d+)/mg) 171 { 172 $cooked .= "Current number of children: $1"; 173 if ($2 > 0) 174 { 175 $cooked .= " (maximum $2)"; 176 } 177 $cooked .= "\n"; 178 $cooked .= "QueueDir free disk space (in blocks): $3\n"; 179 $cooked .= "Load average: $4\n"; 180 } 181 while ($raw =~ /^(\d+) (.*)$/mg) 182 { 183 if (not $daemonStatus) 184 { 185 $daemonStatus = "(process $1) " . ucfirst($2) . "\n"; 186 } 187 else 188 { 189 $cooked .= "Child Process $1 Status: $2\n"; 190 } 191 } 192 return ($daemonStatus, $cooked); 193} 194 195########################################################################## 196# 197# &start_daemon -- fork off a sendmail daemon 198# 199# Parameters: 200# control -- control socket name 201# 202# Returns: 203# Error message or "OK" if successful 204# 205 206sub start_daemon 207{ 208 my $control = shift; 209 my $pid; 210 211 if ($pid = fork) 212 { 213 my $exitstat; 214 215 waitpid $pid, 0 or return "Could not get status of created process: $!\n"; 216 $exitstat = $? / 256; 217 if ($exitstat != 0) 218 { 219 return "sendmail daemon startup exited with exit value $exitstat"; 220 } 221 } 222 elsif (defined $pid) 223 { 224 exec($sendmailDaemon); 225 die "Unable to start sendmail daemon: $!.\n"; 226 } 227 else 228 { 229 return "Could not create new process: $!\n"; 230 } 231 return "OK\n"; 232} 233 234########################################################################## 235# 236# &stop_daemon -- stop the sendmail daemon using control socket 237# 238# Parameters: 239# control -- control socket name 240# 241# Returns: 242# Error message or status message 243# 244 245sub stop_daemon 246{ 247 my $control = shift; 248 my $status; 249 250 if (not defined $control) 251 { 252 return "The control socket is not configured so the daemon can not be stopped.\n"; 253 } 254 return &do_command($control, "SHUTDOWN"); 255} 256 257########################################################################## 258# 259# &restart_daemon -- restart the sendmail daemon using control socket 260# 261# Parameters: 262# control -- control socket name 263# 264# Returns: 265# Error message or status message 266# 267 268sub restart_daemon 269{ 270 my $control = shift; 271 my $status; 272 273 if (not defined $control) 274 { 275 return "The control socket is not configured so the daemon can not be restarted."; 276 } 277 return &do_command($control, "RESTART"); 278} 279 280########################################################################## 281# 282# &memdump -- get memdump from the daemon using the control socket 283# 284# Parameters: 285# control -- control socket name 286# 287# Returns: 288# Error message or status message 289# 290 291sub memdump 292{ 293 my $control = shift; 294 my $status; 295 296 if (not defined $control) 297 { 298 return "The control socket is not configured so the daemon can not be queried for memdump."; 299 } 300 return &do_command($control, "MEMDUMP"); 301} 302 303########################################################################## 304# 305# &help -- get help from the daemon using the control socket 306# 307# Parameters: 308# control -- control socket name 309# 310# Returns: 311# Error message or status message 312# 313 314sub help 315{ 316 my $control = shift; 317 my $status; 318 319 if (not defined $control) 320 { 321 return "The control socket is not configured so the daemon can not be queried for help."; 322 } 323 return &do_command($control, "HELP"); 324} 325 326my $status = undef; 327my $daemonStatus = undef; 328my $opts = {}; 329 330getopts('f:', $opts) || die "Usage: $0 [-f /path/to/control/socket] command\n"; 331 332my $control = $opts->{f} || &get_controlname; 333my $command = shift; 334 335if (not defined $control) 336{ 337 die "No control socket available.\n"; 338} 339if (not defined $command) 340{ 341 die "Usage: $0 [-f /path/to/control/socket] command\n"; 342} 343if ($command eq "status") 344{ 345 $status = &do_command($control, "STATUS"); 346 if (not defined $status) 347 { 348 # Not responding on control channel, query via SMTP 349 if (&sendmail_running) 350 { 351 $daemonStatus = "Sendmail is running but not answering status queries."; 352 } 353 else 354 { 355 $daemonStatus = "Sendmail does not appear to be running."; 356 } 357 } 358 else 359 { 360 # Munge control channel output 361 ($daemonStatus, $status) = &munge_status($status); 362 } 363} 364elsif (lc($command) eq "shutdown") 365{ 366 $status = &stop_daemon($control); 367} 368elsif (lc($command) eq "restart") 369{ 370 $status = &restart_daemon($control); 371} 372elsif (lc($command) eq "start") 373{ 374 $status = &start_daemon($control); 375} 376elsif (lc($command) eq "memdump") 377{ 378 $status = &memdump($control); 379} 380elsif (lc($command) eq "help") 381{ 382 $status = &help($control); 383} 384elsif (lc($command) eq "mstat") 385{ 386 $status = &do_command($control, "mstat"); 387 if (not defined $status) 388 { 389 # Not responding on control channel, query via SMTP 390 if (&sendmail_running) 391 { 392 $daemonStatus = "Sendmail is running but not answering status queries."; 393 } 394 else 395 { 396 $daemonStatus = "Sendmail does not appear to be running."; 397 } 398 } 399} 400else 401{ 402 die "Unrecognized command $command\n"; 403} 404if (defined $daemonStatus) 405{ 406 print "Daemon Status: $daemonStatus\n"; 407} 408if (defined $status) 409{ 410 print "$status\n"; 411} 412else 413{ 414 die "No response\n"; 415} 416