Skip to content
Newer
Older
100755 334 lines (288 sloc) 9.39 KB
29ac286 @aqua - Enable taint checking
aqua authored
1 #!/usr/bin/perl -Tw
04dacc4 Pure perl forking qpsmtpd
Matt Sergeant authored
2 # Copyright (c) 2001 Ask Bjoern Hansen. See the LICENSE file for details.
3 # The "command dispatch" system is taken from colobus - http://trainedmonkey.com/colobus/
4 #
5 # For more information see http://develooper.com/code/qpsmtpd/
6 #
7 #
8
9 use lib 'lib';
10 use Qpsmtpd::TcpServer;
11 use Qpsmtpd::Constants;
12 use IO::Socket;
698fc01 Make pid-file optional
Matt Sergeant authored
13 use IO::Select;
04dacc4 Pure perl forking qpsmtpd
Matt Sergeant authored
14 use Socket;
29ac286 @aqua - Enable taint checking
aqua authored
15 use Getopt::Long;
f84bd18 Slightly better signal handling - may help stability issues for some …
Matt Sergeant authored
16 use POSIX qw(:sys_wait_h :errno_h :signal_h);
04dacc4 Pure perl forking qpsmtpd
Matt Sergeant authored
17 use strict;
18 $| = 1;
19
d8c8d40 @rspier - move configuration to top. (still suboptimal)
rspier authored
20 # Configuration
1e68345 @aqua Clean up whitespace (mainloop had a swath of 4-space indentation, whi…
aqua authored
21 my $MAXCONN = 15; # max simultaneous connections
4a824a2 Improve support for listening to multiple ports and/or multiple IP ad…
John Peacock authored
22 my @PORT; # port number(s)
1e68345 @aqua Clean up whitespace (mainloop had a swath of 4-space indentation, whi…
aqua authored
23 my @LOCALADDR; # ip address(es) to bind to
24 my $USER = 'smtpd'; # user to suid to
25 my $MAXCONNIP = 5; # max simultaneous connections from one IP
698fc01 Make pid-file optional
Matt Sergeant authored
26 my $PID_FILE = '';
a2064bc @aqua Add --detach commandline option to forkserver; if supplied, daemonize…
aqua authored
27 my $DETACH; # daemonize on startup
d8c8d40 @rspier - move configuration to top. (still suboptimal)
rspier authored
28
29ac286 @aqua - Enable taint checking
aqua authored
29 sub usage {
30 print <<"EOT";
31 usage: qpsmtpd-forkserver [ options ]
1fbfe51 @aqua Implement listening on multiple local addresses simultaneously, if sp…
aqua authored
32 -l, --listen-address addr : listen on specific address(es); can be specified
33 multiple times for multiple bindings. Default is
4a824a2 Improve support for listening to multiple ports and/or multiple IP ad…
John Peacock authored
34 0.0.0.0 (all interfaces).
35 -p, --port P : listen on a specific port; default 2525; can be
36 specified multiple times for multiple bindings.
29ac286 @aqua - Enable taint checking
aqua authored
37 -c, --limit-connections N : limit concurrent connections to N; default 15
2847144 @aqua Fix typo in forkserver commandline help
aqua authored
38 -u, --user U : run as a particular user (default 'smtpd')
22a1d99 @rspier From: Jim Winstead
rspier authored
39 -m, --max-from-ip M : limit connections from a single IP; default 5
03f8c0d @abh
authored
40 --pid-file P : print main servers PID to file P
a2064bc @aqua Add --detach commandline option to forkserver; if supplied, daemonize…
aqua authored
41 -d, --detach : detach from controlling terminal (daemonize)
29ac286 @aqua - Enable taint checking
aqua authored
42 EOT
43 exit 0;
44 }
45
46 GetOptions('h|help' => \&usage,
1fbfe51 @aqua Implement listening on multiple local addresses simultaneously, if sp…
aqua authored
47 'l|listen-address=s' => \@LOCALADDR,
29ac286 @aqua - Enable taint checking
aqua authored
48 'c|limit-connections=i' => \$MAXCONN,
22a1d99 @rspier From: Jim Winstead
rspier authored
49 'm|max-from-ip=i' => \$MAXCONNIP,
4a824a2 Improve support for listening to multiple ports and/or multiple IP ad…
John Peacock authored
50 'p|port=s' => \@PORT,
03f8c0d @abh
authored
51 'u|user=s' => \$USER,
698fc01 Make pid-file optional
Matt Sergeant authored
52 'pid-file=s' => \$PID_FILE,
a2064bc @aqua Add --detach commandline option to forkserver; if supplied, daemonize…
aqua authored
53 'd|detach' => \$DETACH,
4a824a2 Improve support for listening to multiple ports and/or multiple IP ad…
John Peacock authored
54 ) || &usage;
29ac286 @aqua - Enable taint checking
aqua authored
55
56 # detaint the commandline
1fbfe51 @aqua Implement listening on multiple local addresses simultaneously, if sp…
aqua authored
57 @LOCALADDR = ( '0.0.0.0' ) if !@LOCALADDR;
4a824a2 Improve support for listening to multiple ports and/or multiple IP ad…
John Peacock authored
58 @PORT = ( 2525 ) if !@PORT;
59
60 my @LISTENADDR;
1fbfe51 @aqua Implement listening on multiple local addresses simultaneously, if sp…
aqua authored
61 for (0..$#LOCALADDR) {
2c683f2 Implement multiple IP:PORT listen in forkserver (Devin Carraway).
John Peacock authored
62 if ($LOCALADDR[$_] =~ /^([\d\w\-.]+)(?::(\d+))?$/) {
4a824a2 Improve support for listening to multiple ports and/or multiple IP ad…
John Peacock authored
63 if ( defined $2 ) {
64 push @LISTENADDR, { 'addr' => $1, 'port' => $2 };
65 } else {
66 my $addr = $1;
67 for (0..$#PORT) {
68 if ( $PORT[$_] =~ /^(\d+)$/ ) {
69 push @LISTENADDR, { 'addr' => $addr, 'port' => $1 };
70 } else {
71 &usage;
72 }
73 }
74 }
1fbfe51 @aqua Implement listening on multiple local addresses simultaneously, if sp…
aqua authored
75 } else {
76 &usage;
77 }
78 }
4a824a2 Improve support for listening to multiple ports and/or multiple IP ad…
John Peacock authored
79
29ac286 @aqua - Enable taint checking
aqua authored
80 if ($USER =~ /^([\w\-]+)$/) { $USER = $1 } else { &usage }
81 if ($MAXCONN =~ /^(\d+)$/) { $MAXCONN = $1 } else { &usage }
82
04dacc4 Pure perl forking qpsmtpd
Matt Sergeant authored
83 delete $ENV{ENV};
84 $ENV{PATH} = '/bin:/usr/bin:/var/qmail/bin';
85
d8c8d40 @rspier - move configuration to top. (still suboptimal)
rspier authored
86 my %childstatus = ();
87
04dacc4 Pure perl forking qpsmtpd
Matt Sergeant authored
88 sub REAPER {
d8c8d40 @rspier - move configuration to top. (still suboptimal)
rspier authored
89 while ( defined(my $chld = waitpid(-1, WNOHANG)) ){
90 last unless $chld > 0;
e331f6b Add plugable logging support include sample plugin which replicates the
John Peacock authored
91 ::log(LOGINFO,"cleaning up after $chld");
d8c8d40 @rspier - move configuration to top. (still suboptimal)
rspier authored
92 delete $childstatus{$chld};
93 }
04dacc4 Pure perl forking qpsmtpd
Matt Sergeant authored
94 }
95
5d40964 Make signal handling slightly more stable
Matt Sergeant authored
96 sub HUNTSMAN {
97 $SIG{CHLD} = 'DEFAULT';
98 kill 'INT' => keys %childstatus;
00e06cc @aqua Remove PID file on exit, if we were told to create one with --pid-file
aqua authored
99 if ($PID_FILE && -e $PID_FILE) {
a4e4c52 @aqua Fix whitespace (spaces for a tab)
aqua authored
100 unlink $PID_FILE or ::log(LOGERROR, "unlink: $PID_FILE: $!");
00e06cc @aqua Remove PID file on exit, if we were told to create one with --pid-file
aqua authored
101 }
5d40964 Make signal handling slightly more stable
Matt Sergeant authored
102 exit(0);
103 }
104
105 $SIG{INT} = \&HUNTSMAN;
106 $SIG{TERM} = \&HUNTSMAN;
04dacc4 Pure perl forking qpsmtpd
Matt Sergeant authored
107
1fbfe51 @aqua Implement listening on multiple local addresses simultaneously, if sp…
aqua authored
108 my $select = new IO::Select;
109
110 # establish SERVER socket(s), bind and listen.
4a824a2 Improve support for listening to multiple ports and/or multiple IP ad…
John Peacock authored
111 for my $listen_addr (@LISTENADDR) {
2c683f2 Implement multiple IP:PORT listen in forkserver (Devin Carraway).
John Peacock authored
112 my $server = IO::Socket::INET->new(LocalPort => $listen_addr->{'port'},
113 LocalAddr => $listen_addr->{'addr'},
1fbfe51 @aqua Implement listening on multiple local addresses simultaneously, if sp…
aqua authored
114 Proto => 'tcp',
115 Reuse => 1,
116 Blocking => 0,
117 Listen => SOMAXCONN )
2c683f2 Implement multiple IP:PORT listen in forkserver (Devin Carraway).
John Peacock authored
118 or die "Creating TCP socket $listen_addr->{'addr'}:$listen_addr->{'port'}: $!\n";
1fbfe51 @aqua Implement listening on multiple local addresses simultaneously, if sp…
aqua authored
119 IO::Handle::blocking($server, 0);
120 $select->add($server);
121 }
04dacc4 Pure perl forking qpsmtpd
Matt Sergeant authored
122
698fc01 Make pid-file optional
Matt Sergeant authored
123 if ($PID_FILE) {
124 if ($PID_FILE =~ m#^(/[\w\d/\-.]+)$#) { $PID_FILE = $1 } else { &usage }
125 if (-e $PID_FILE) {
126 open PID, "+<$PID_FILE"
127 or die "open pid_file: $!\n";
da5c0a7 @aqua Fix unitialized-value warning if the PID file existed but was zero-le…
aqua authored
128 my $running_pid = <PID> || ''; chomp $running_pid;
698fc01 Make pid-file optional
Matt Sergeant authored
129 if ($running_pid =~ /(\d+)/) {
130 $running_pid = $1;
131 if (kill 0, $running_pid) {
132 die "Found an already running qpsmtpd with pid $running_pid.\n";
133 }
03f8c0d @abh
authored
134 }
698fc01 Make pid-file optional
Matt Sergeant authored
135 seek PID, 0, 0
136 or die "Could not seek back to beginning of $PID_FILE: $!\n";
a3ff03f @aqua Merge r493 from trunk to truncate PID file before re-use
aqua authored
137 truncate PID, 0
138 or die "Could not truncate $PID_FILE at 0: $!";
698fc01 Make pid-file optional
Matt Sergeant authored
139 } else {
140 open PID, ">$PID_FILE"
141 or die "open pid_file: $!\n";
03f8c0d @abh
authored
142 }
143 }
698fc01 Make pid-file optional
Matt Sergeant authored
144
145 # Load plugins here
146 my $qpsmtpd = Qpsmtpd::TcpServer->new();
03f8c0d @abh
authored
147
148 # Drop privileges
d8c8d40 @rspier - move configuration to top. (still suboptimal)
rspier authored
149 my (undef, undef, $quid, $qgid) = getpwnam $USER or
150 die "unable to determine uid/gid for $USER\n";
03f8c0d @abh
authored
151 my $groups = "$qgid $qgid";
152 while (my ($name,$passwd,$gid,$members) = getgrent()) {
153 my @m = split(/ /, $members);
154 if (grep {$_ eq $USER} @m) {
4a824a2 Improve support for listening to multiple ports and/or multiple IP ad…
John Peacock authored
155 $groups .= " $gid";
03f8c0d @abh
authored
156 }
157 }
158 $) = $groups;
04dacc4 Pure perl forking qpsmtpd
Matt Sergeant authored
159 POSIX::setgid($qgid) or
160 die "unable to change gid: $!\n";
161 POSIX::setuid($quid) or
162 die "unable to change uid: $!\n";
163 $> = $quid;
164
239daaf @aqua Drop root privileges before loading plugins, rather than after. This …
aqua authored
165 $qpsmtpd->load_plugins;
166
4a824a2 Improve support for listening to multiple ports and/or multiple IP ad…
John Peacock authored
167 foreach my $listen_addr ( @LISTENADDR ) {
168 ::log(LOGINFO,"Listening on $listen_addr->{'addr'}:$listen_addr->{'port'}");
2c683f2 Implement multiple IP:PORT listen in forkserver (Devin Carraway).
John Peacock authored
169 }
e331f6b Add plugable logging support include sample plugin which replicates the
John Peacock authored
170 ::log(LOGINFO, 'Running as user '.
4a824a2 Improve support for listening to multiple ports and/or multiple IP ad…
John Peacock authored
171 (getpwuid($>) || $>) .
172 ', group '.
173 (getgrgid($)) || $)));
04dacc4 Pure perl forking qpsmtpd
Matt Sergeant authored
174
a2064bc @aqua Add --detach commandline option to forkserver; if supplied, daemonize…
aqua authored
175 if ($DETACH) {
176 open STDIN, '/dev/null' or die "/dev/null: $!";
177 open STDOUT, '>/dev/null' or die "/dev/null: $!";
178 open STDERR, '>&STDOUT' or die "open(stderr): $!";
179 defined (my $pid = fork) or die "fork: $!";
180 exit 0 if $pid;
181 POSIX::setsid or die "setsid: $!";
182 }
183
184 if ($PID_FILE) {
185 print PID $$,"\n";
186 close PID;
187 }
188
9cbf206 * lib/Qpsmtpd/TcpServer.pm
John Peacock authored
189 # Populate class cached variables
190 $qpsmtpd->spool_dir;
191 $qpsmtpd->size_threshold;
192
04dacc4 Pure perl forking qpsmtpd
Matt Sergeant authored
193 while (1) {
698fc01 Make pid-file optional
Matt Sergeant authored
194 REAPER();
d8c8d40 @rspier - move configuration to top. (still suboptimal)
rspier authored
195 my $running = scalar keys %childstatus;
314625d Another small cleanup
Matt Sergeant authored
196 if ($running >= $MAXCONN) {
d8c8d40 @rspier - move configuration to top. (still suboptimal)
rspier authored
197 ::log(LOGINFO,"Too many connections: $running >= $MAXCONN. Waiting one second.");
4b72a40 Minor cleanup
Matt Sergeant authored
198 sleep(1);
199 next;
d8c8d40 @rspier - move configuration to top. (still suboptimal)
rspier authored
200 }
1fbfe51 @aqua Implement listening on multiple local addresses simultaneously, if sp…
aqua authored
201 my @ready = $select->can_read(1);
202 next if !@ready;
3fc6a4f Make sure we process all servers after select()
Matt Sergeant authored
203 while (my $server = shift @ready) {
204 my ($client, $hisaddr) = $server->accept;
205
206 if (!$hisaddr) {
207 # possible something condition...
208 next;
b82536d Support per-IP throttling (Hanno Hecker <hah@uu-x.de>)
Matt Sergeant authored
209 }
3fc6a4f Make sure we process all servers after select()
Matt Sergeant authored
210 IO::Handle::blocking($client, 1);
211 my ($port, $iaddr) = sockaddr_in($hisaddr);
c092034 the pre-connection and post-connection hooks are not working in
John Peacock authored
212 my $localsockaddr = getsockname($client);
213 my ($lport, $laddr) = sockaddr_in($localsockaddr);
214
215 my ($rc, @msg) = $qpsmtpd->run_hooks("pre-connection",
216 remote_ip => inet_ntoa($iaddr),
217 remote_port => $port,
218 local_ip => inet_ntoa($laddr),
219 local_port => $lport,
220 max_conn_ip => $MAXCONNIP,
221 child_addrs => [values %childstatus],
222 );
223 if ($rc == DENYSOFT || $rc == DENYSOFT_DISCONNECT) {
224 unless ($msg[0]) {
225 @msg = ("Sorry, try again later");
3fc6a4f Make sure we process all servers after select()
Matt Sergeant authored
226 }
c092034 the pre-connection and post-connection hooks are not working in
John Peacock authored
227 &respond_client($client, 451, @msg);
228 close $client;
229 next;
230 }
231 elsif ($rc == DENY || $rc == DENY_DISCONNECT) {
232 unless ($msg[0]) {
233 @msg = ("Sorry, service not available for you");
3fc6a4f Make sure we process all servers after select()
Matt Sergeant authored
234 }
c092034 the pre-connection and post-connection hooks are not working in
John Peacock authored
235 &respond_client($client, 550, @msg);
236 close $client;
237 next;
3fc6a4f Make sure we process all servers after select()
Matt Sergeant authored
238 }
c092034 the pre-connection and post-connection hooks are not working in
John Peacock authored
239
3fc6a4f Make sure we process all servers after select()
Matt Sergeant authored
240 my $pid = safe_fork();
241 if ($pid) {
242 # parent
4a824a2 Improve support for listening to multiple ports and/or multiple IP ad…
John Peacock authored
243 $childstatus{$pid} = $iaddr; # add to table
244 # $childstatus{$pid} = 1; # add to table
3fc6a4f Make sure we process all servers after select()
Matt Sergeant authored
245 $running++;
246 close($client);
1e68345 @aqua Clean up whitespace (mainloop had a swath of 4-space indentation, whi…
aqua authored
247 next;
f84bd18 Slightly better signal handling - may help stability issues for some …
Matt Sergeant authored
248 }
3fc6a4f Make sure we process all servers after select()
Matt Sergeant authored
249 # otherwise child
250
251 # all children should have different seeds, to prevent conflicts
252 srand( time ^ ($$ + ($$ << 15)) );
1e68345 @aqua Clean up whitespace (mainloop had a swath of 4-space indentation, whi…
aqua authored
253
3fc6a4f Make sure we process all servers after select()
Matt Sergeant authored
254 close($server);
1e68345 @aqua Clean up whitespace (mainloop had a swath of 4-space indentation, whi…
aqua authored
255
3fc6a4f Make sure we process all servers after select()
Matt Sergeant authored
256 $SIG{$_} = 'DEFAULT' for keys %SIG;
257 $SIG{ALRM} = sub {
258 print $client "421 Connection Timed Out\n";
259 ::log(LOGINFO, "Connection Timed Out");
260 exit; };
1e68345 @aqua Clean up whitespace (mainloop had a swath of 4-space indentation, whi…
aqua authored
261
3fc6a4f Make sure we process all servers after select()
Matt Sergeant authored
262 $ENV{TCPLOCALIP} = inet_ntoa($laddr);
263 # my ($port, $iaddr) = sockaddr_in($hisaddr);
264 $ENV{TCPREMOTEIP} = inet_ntoa($iaddr);
265 $ENV{TCPREMOTEHOST} = gethostbyaddr($iaddr, AF_INET) || "Unknown";
266
267 # don't do this!
268 #$0 = "qpsmtpd-forkserver: $ENV{TCPREMOTEIP} / $ENV{TCPREMOTEHOST}";
269
270 ::log(LOGINFO, "Accepted connection $running/$MAXCONN from $ENV{TCPREMOTEIP} / $ENV{TCPREMOTEHOST}");
271
272 # dup to STDIN/STDOUT
273 POSIX::dup2(fileno($client), 0);
274 POSIX::dup2(fileno($client), 1);
275
276 $qpsmtpd->start_connection
277 (
278 local_ip => $ENV{TCPLOCALIP},
279 local_port => $lport,
280 remote_ip => $ENV{TCPREMOTEIP},
281 remote_port => $port,
282 );
283 $qpsmtpd->run();
284
c092034 the pre-connection and post-connection hooks are not working in
John Peacock authored
285 $qpsmtpd->run_hooks("post-connection");
3fc6a4f Make sure we process all servers after select()
Matt Sergeant authored
286 exit; # child leaves
287 }
04dacc4 Pure perl forking qpsmtpd
Matt Sergeant authored
288 }
289
d8c8d40 @rspier - move configuration to top. (still suboptimal)
rspier authored
290 sub log {
291 my ($level,$message) = @_;
6620034 * qpsmtpd-forkserver
John Peacock authored
292 $qpsmtpd->log($level,$message);
d8c8d40 @rspier - move configuration to top. (still suboptimal)
rspier authored
293 }
294
c092034 the pre-connection and post-connection hooks are not working in
John Peacock authored
295 sub respond_client {
296 my ($client, $code, @message) = @_;
297 $client->autoflush(1);
298 while (my $msg = shift @message) {
299 my $line = $code . (@message?"-":" ").$msg;
300 ::log(LOGDEBUG, $line);
301 print $client "$line\r\n"
302 or (::log(LOGERROR, "Could not print [$line]: $!"), return 0);
303 }
304 return 1;
305 }
306
698fc01 Make pid-file optional
Matt Sergeant authored
307 ### routine to protect process during fork
308 sub safe_fork {
309
310 ### block signal for fork
311 my $sigset = POSIX::SigSet->new(SIGINT);
312 POSIX::sigprocmask(SIG_BLOCK, $sigset)
313 or die "Can't block SIGINT for fork: [$!]\n";
314
315 ### fork off a child
316 my $pid = fork;
317 unless( defined $pid ){
318 die "Couldn't fork: [$!]\n";
319 }
320
321 ### make SIGINT kill us as it did before
322 $SIG{INT} = 'DEFAULT';
323
324 ### put back to normal
325 POSIX::sigprocmask(SIG_UNBLOCK, $sigset)
326 or die "Can't unblock SIGINT for fork: [$!]\n";
327
328 return $pid;
329 }
330
04dacc4 Pure perl forking qpsmtpd
Matt Sergeant authored
331 __END__
332
333 1;
Something went wrong with that request. Please try again.