smcontrol.pl revision 266692
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