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