File Coverage

blib/lib/DJabberd.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3 17     17   7634 use strict;
  17         24  
  17         398  
4 17     17   56 use Carp;
  17         23  
  17         922  
5 17     17   8488 use Danga::Socket 1.51;
  17         343243  
  17         512  
6 17     17   7500 use IO::Socket::INET;
  17         196713  
  17         121  
7 17     17   6600 use IO::Socket::UNIX;
  17         26  
  17         104  
8 17     17   10277 use POSIX ();
  17         27  
  17         286  
9              
10 17     17   8306 use DJabberd::VHost;
  0            
  0            
11             use DJabberd::Callback;
12             use Scalar::Util;
13             use DJabberd::HookDocs;
14             use DJabberd::Connection;
15             use DJabberd::Connection::ServerIn;
16             use DJabberd::Connection::ClientIn;
17             use DJabberd::Connection::ClusterIn;
18             use DJabberd::Connection::OldSSLClientIn;
19             use DJabberd::Connection::Admin;
20              
21             use DJabberd::Stanza::StartTLS;
22             use DJabberd::Stanza::SASL;
23             use DJabberd::Stanza::StreamFeatures;
24             use DJabberd::Stanza::DialbackVerify;
25             use DJabberd::Stanza::DialbackResult;
26             use DJabberd::JID;
27             use DJabberd::IQ;
28             use DJabberd::Message;
29             use DJabberd::Presence;
30             use DJabberd::StreamVersion;
31             use DJabberd::Log;
32              
33             use DJabberd::Delivery::Local;
34             use DJabberd::Delivery::S2S;
35             use DJabberd::PresenceChecker::Local;
36              
37             use DJabberd::Stats;
38              
39             package DJabberd;
40             use strict;
41             use Socket qw(IPPROTO_TCP TCP_NODELAY SOL_SOCKET SOCK_STREAM);
42             use Carp qw(croak);
43             use DJabberd::Util qw(tsub as_bool as_num as_abs_path as_bind_addr);
44              
45             our $VERSION = '0.85_01';
46              
47             our $logger = DJabberd::Log->get_logger();
48             our $hook_logger = DJabberd::Log->get_logger("DJabberd::Hook");
49              
50             our %server;
51              
52             $SIG{USR2} = sub { Carp::cluck("USR2") };
53              
54             sub new {
55             my ($class, %opts) = @_;
56              
57             my $self = {
58             'daemonize' => delete $opts{daemonize},
59             's2s_port' => delete $opts{s2s_port},
60             'c2s_port' => delete($opts{c2s_port}) || 5222, # {=clientportnumber}
61             'old_ssl' => delete $opts{old_ssl},
62             'vhosts' => {},
63             'fake_peers' => {}, # for s2s testing. $hostname => "ip:port"
64             'share_parsers' => 1,
65             'monitor_host' => {},
66             };
67              
68             # if they set s2s_port to explicitly 0, it's disabled for all vhosts
69             # but not setting it means 5269 still listens, if vhosts are configured
70             # for s2s.
71             # {=serverportnumber}
72             $self->{s2s_port} = 5269 unless defined $self->{s2s_port};
73              
74             croak("Unknown server parameters: " . join(", ", keys %opts)) if %opts;
75              
76             bless $self, $class;
77             $server{$self} = $self;
78             Scalar::Util::weaken($server{$self});
79              
80             return $self;
81             }
82              
83             sub DESTROY {
84             delete $server{$_[0]};
85             }
86              
87             # class method
88             sub foreach_vhost {
89             my (undef, $cb) = @_;
90             foreach my $server (values %DJabberd::server) {
91             foreach my $vhost (values %{$server->{vhosts}}) {
92             $cb->($vhost);
93             }
94             }
95             }
96              
97             sub share_parsers { $_[0]{share_parsers} };
98              
99             sub set_config_shareparsers {
100             my ($self, $val) = @_;
101             $self->{share_parsers} = as_bool($val);
102             }
103              
104             sub set_config_declaremonitor {
105             my ($self, $val) = @_;
106             $self->{monitor_host}{$val} = 1;
107             }
108              
109             # mimicing Apache's SSLCertificateKeyFile config
110             sub set_config_sslcertificatekeyfile {
111             my ($self, $val) = @_;
112             $self->{ssl_private_key_file} = as_abs_path($val);
113             }
114              
115             # mimicing Apache's SSLCertificateFile
116             sub set_config_sslcertificatefile {
117             my ($self, $val) = @_;
118             $self->{ssl_cert_file} = as_abs_path($val);
119             }
120              
121             sub ssl_private_key_file { return $_[0]{ssl_private_key_file} }
122             sub ssl_cert_file { return $_[0]{ssl_cert_file} }
123              
124             sub set_config_oldssl {
125             my ($self, $val) = @_;
126             $self->{old_ssl} = as_bool($val);
127             }
128              
129             sub set_config_unixdomainsocket {
130             my ($self, $val) = @_;
131             $self->{unixdomainsocket} = $val;
132             }
133              
134             sub set_config_clientport {
135             my ($self, $val) = @_;
136             $self->{c2s_port} = as_bind_addr($val);
137             }
138              
139             sub set_config_serverport {
140             my ($self, $val) = @_;
141             $self->{s2s_port} = as_bind_addr($val);
142             }
143              
144             sub set_config_adminport {
145             my ($self, $val) = @_;
146             $self->{admin_port} = as_bind_addr($val);
147             }
148              
149             sub set_config_intradomainlisten {
150             my ($self, $val) = @_;
151             $self->{cluster_listen} = $val;
152             }
153              
154             sub set_config_pidfile {
155             my ($self, $val) = @_;
156             $self->{pid_file} = $val;
157             }
158              
159             our %fake_peers;
160             sub set_fake_s2s_peer {
161             my ($self, $host, $ipendpt) = @_;
162             $fake_peers{$host} = $ipendpt;
163             }
164              
165             sub fake_s2s_peer {
166             my ($self, $host) = @_;
167             return $fake_peers{$host};
168             }
169              
170             sub set_config_casesensitive {
171             my ($self, $val) = @_;
172             $DJabberd::JID::CASE_SENSITIVE = as_bool($val);
173             }
174              
175             sub add_vhost {
176             my ($self, $vhost) = @_;
177             my $sname = lc $vhost->name;
178             if (my $existing = $self->{vhosts}{$sname}) {
179             croak("Can't set vhost with name '$sname'. Already exists in this server.")
180             if $existing != $vhost;
181             }
182             if ($vhost->server && $vhost->server != $self) {
183             croak("Vhost already has a server.");
184             }
185              
186             $vhost->setup_default_plugins;
187              
188             $self->{vhosts}{$sname} = $vhost;
189             $vhost->set_server($self);
190             }
191              
192             # works as Server method or class method.
193             sub lookup_vhost {
194             my ($self, $hostname) = @_;
195              
196             # look at all server objects in process
197             unless (ref $self) {
198             foreach my $server (values %DJabberd::server) {
199             my $vh = $server->lookup_vhost($hostname);
200             return $vh if $vh;
201             }
202             return 0;
203             }
204              
205             # method on server object
206             foreach my $vhost (values %{$self->{vhosts}}) {
207             return $vhost
208             if ($vhost->handles_domain($hostname));
209             }
210             return 0;
211             }
212              
213             # return the version of the spec we implement
214             sub spec_version {
215             my $self = shift;
216             return $self->{_spec_version} ||= DJabberd::StreamVersion->new("1.0");
217             }
218              
219              
220             my %obj_source; # refaddr -> file/linenumber
221             my %obj_living; # file/linenumber -> ct
222             use Scalar::Util qw(refaddr weaken);
223             use Data::Dumper;
224             sub dump_obj_stats {
225             print Dumper(\%obj_living);
226             my %class_ct;
227             foreach (values %obj_source) {
228             $class_ct{ref($_->[1])}++;
229             }
230             print Dumper(\%class_ct);
231             }
232              
233              
234             sub track_new_obj {
235             return unless $ENV{TRACKOBJ};
236              
237             my ($class, $obj) = @_;
238             my $i = 0;
239             my $fileline;
240             while (!$fileline) {
241             $i++;
242             my ($pkg, $filename, $line, $subname) = caller($i);
243             next if $subname eq "new";
244             $fileline = "$filename/$line";
245             }
246             my $addr = refaddr($obj);
247             warn "New object $obj -- $fileline\n" if $ENV{TRACKOBJ};
248             $obj_source{$addr} = [$fileline, $obj];
249             weaken($obj_source{$addr}[1]);
250              
251             $obj_living{$fileline}++;
252             dump_obj_stats() if $ENV{TRACKOBJ};
253             }
254              
255             sub track_destroyed_obj {
256             return unless $ENV{TRACKOBJ};
257              
258             my ($class, $obj) = @_;
259             my $addr = refaddr($obj);
260             my $fileline = $obj_source{$addr}->[0] or die "Where did $obj come from?";
261             delete $obj_source{$addr};
262             warn "Destroyed object $obj -- $fileline\n" if $ENV{TRACKOBJ};
263             $obj_living{$fileline}--;
264             dump_obj_stats() if $ENV{TRACKOBJ};
265             }
266              
267             sub debug {
268             my $self = shift;
269             return unless $self->{debug};
270             printf STDERR @_;
271             }
272              
273             sub run {
274             my $self = shift;
275             daemonize() if $self->{daemonize};
276             local $SIG{'PIPE'} = "IGNORE"; # handled manually
277             if ($self->{pid_file}) {
278             $logger->debug("Logging PID to file $self->{pid_file}");
279             open(PIDFILE,'>',$self->{pid_file}) or $logger->logdie("Can't open pidfile $self->{pid_file} for writing");
280             print PIDFILE "$$\n";
281             close(PIDFILE);
282             }
283             $self->start_c2s_server();
284              
285             # {=s2soptional}
286             $self->start_s2s_server() if $self->{s2s_port};
287              
288             $self->start_cluster_server() if $self->{cluster_listen};
289              
290             $self->_start_server($self->{admin_port}, "DJabberd::Connection::Admin") if $self->{admin_port};
291              
292             DJabberd::Connection::Admin->on_startup;
293             Danga::Socket->EventLoop();
294             unlink($self->{pid_file}) if (-f $self->{pid_file});
295             }
296              
297             sub _start_server {
298             my ($self, $localaddr, $class) = @_;
299              
300             # establish SERVER socket, bind and listen.
301             my $server;
302             my $not_tcp = 0;
303             if ($localaddr =~ m!^/!) {
304             $not_tcp = 1;
305             $server = IO::Socket::UNIX->new(Type => SOCK_STREAM,
306             Local => $localaddr,
307             Listen => 10)
308             or $logger->logdie("Error creating unix domain socket: $@\n");
309             } else {
310             # assume it's a port if there's no colon
311             unless ($localaddr =~ /:/) {
312             $localaddr = "0.0.0.0:$localaddr";
313             }
314              
315             $logger->debug("Opening TCP listen socket on $localaddr");
316              
317             $server = IO::Socket::INET->new(LocalAddr => $localaddr,
318             Type => SOCK_STREAM,
319             Proto => IPPROTO_TCP,
320             Reuse => 1,
321             Listen => 10 )
322             or $logger->logdie("Error creating socket: $@\n");
323              
324             my $success = $server->blocking(0);
325              
326             unless (defined($success)) {
327             if ($^O eq 'MSWin32') {
328             # On Windows, we have to do this a bit differently
329             my $do = 1;
330             ioctl($server, 0x8004667E, \$do) or $logger->warn("Failed to make socket non-blocking: $!");
331             }
332             else {
333             $logger->warn("Failed to make socket non-blocking: $!")
334             }
335             }
336             }
337              
338             # Not sure if I'm crazy or not, but I can't see in strace where/how
339             # Perl 5.6 sets blocking to 0 without this. In Perl 5.8, IO::Socket::INET
340             # obviously sets it from watching strace.
341             IO::Handle::blocking($server, 0);
342              
343             my $accept_handler = sub {
344             local *__ANON__ = " Accept handler in ". __FILE__ ." on line ". __LINE__;
345              
346             my $csock = $server->accept;
347             return unless $csock;
348              
349             $self->debug("Listen child making a DJabberd::Connection for %d.\n", fileno($csock));
350              
351             IO::Handle::blocking($csock, 0);
352             unless ($not_tcp) {
353             setsockopt($csock, IPPROTO_TCP, TCP_NODELAY, pack("l", 1)) or die;
354             }
355              
356             if (my $client = eval { $class->new($csock, $self) }) {
357             $DJabberd::Stats::counter{connect}++;
358             $client->watch_read(1);
359             return;
360             } else {
361             $logger->error("Error creating new $class: $@");
362             }
363             };
364              
365             Danga::Socket->AddOtherFds(fileno($server) => $accept_handler);
366             }
367              
368             sub start_c2s_server {
369             my $self = shift;
370             $self->_start_server($self->{c2s_port},
371             "DJabberd::Connection::ClientIn");
372              
373             if ($self->{old_ssl}) {
374             $self->_start_server(5223, "DJabberd::Connection::OldSSLClientIn");
375             }
376              
377             if ($self->{unixdomainsocket}) {
378             $self->_start_server($self->{unixdomainsocket}, "DJabberd::Connection::ClientIn");
379             }
380             }
381              
382             sub start_s2s_server {
383             my $self = shift;
384             $self->_start_server($self->{s2s_port},
385             "DJabberd::Connection::ServerIn");
386             }
387              
388             sub start_cluster_server {
389             my $self = shift;
390             $self->_start_server($self->{cluster_listen},
391             "DJabberd::Connection::ClusterIn");
392             }
393              
394             sub start_simple_server {
395             my ($self, $port) = @_;
396             eval "use DJabberd::Connection::SimpleIn; 1"
397             or die "Failed to load DJabberd::Connection::SimpleIn: $@\n";
398             $self->_start_server($port, "DJabberd::Connection::SimpleIn");
399             }
400              
401             sub is_monitor_host {
402             my ($self, $host) = @_;
403             return $self->{monitor_host}{$host};
404             }
405              
406             sub load_config {
407             my ($self, $arg) = @_;
408             if (ref $arg eq "SCALAR") {
409             $self->_load_config_ref($arg);
410             } else {
411             open (my $fh, $arg) or die "Couldn't open config file '$arg': $!\n";
412             my $slurp = do { local $/; <$fh>; };
413             $self->_load_config_ref(\$slurp);
414             }
415             }
416              
417             sub _load_config_ref {
418             my ($self, $configref) = @_;
419             my $linenum = 0;
420             my $vhost; # current vhost in scope
421             my $plugin; # current plugin in scope
422             my @vhost_stack = ();
423              
424             my $expand_var = sub {
425             my ($type, $key) = @_;
426             $type = uc $type;
427              
428             if ($type eq "ENV") {
429             # expands ${ENV:KEY} on a line into $ENV{'KEY'} or dies if not defined
430             my $val = $ENV{$key};
431             die "Undefined environment variable '$key'\n" unless defined $val;
432             return $val;
433             }
434             die "Unknown variable type '$type'\n";
435             };
436              
437             foreach my $line (split(/\n/, $$configref)) {
438             $linenum++;
439              
440             $line =~ s/^\s+//;
441             next if $line =~ /^\#/;
442             $line =~ s/\s+$//;
443             next unless $line =~ /\S/;
444              
445             eval {
446             # expand environment variables
447             $line =~ s/\$\{(\w+):(\w+)\}/$expand_var->($1, $2)/eg;
448              
449             if ($line =~ /^(\w+)\s+(.+)/) {
450             my $pkey = $1;
451             my $key = lc $1;
452             my $val = $2;
453             my $inv = $plugin || $vhost || $self;
454             my $meth = "set_config_$key";
455             if ($inv->can($meth)) {
456             $inv->$meth($val);
457             next;
458             }
459             $meth = "set_config__option";
460             if ($inv->can($meth)) {
461             $inv->$meth($key, $val);
462             next;
463             }
464              
465             die "Unknown option '$pkey'\n";
466             }
467             if ($line =~ //i) {
468             die "Can't configure a vhost in a vhost\n" if $vhost;
469             $vhost = DJabberd::VHost->new(server_name => $1);
470             $vhost->set_server($self);
471             next;
472             }
473             if ($line =~ m!!i) {
474             die "Can't end a not-open vhost\n" unless $vhost;
475             die "Can't end a vhost with an open plugin\n" if $plugin;
476             die "Can't end a vhost with an open subdomain\n" if @vhost_stack;
477             $self->add_vhost($vhost);
478             $vhost = undef;
479             next;
480             }
481             if ($line =~ //i) {
482             die "Subdomain blocks can only inside VHost\n" unless $vhost;
483             my $subdomain_name = $1.".".$vhost->server_name;
484              
485             my $old_vhost = $vhost;
486             push @vhost_stack, $old_vhost;
487              
488             $vhost = DJabberd::VHost->new(server_name => $subdomain_name);
489             $vhost->set_server($self);
490              
491             # Automatically add the LocalVHosts delivery plugin so that these
492             # VHosts can talk to one another without S2S.
493             my $loaded = eval "use DJabberd::Delivery::LocalVHosts; 1;";
494             die "Failed to load LocalVHosts delivery plugin for subdomain" unless $loaded;
495              
496             my $ld1 = DJabberd::Delivery::LocalVHosts->new(allowvhost => $subdomain_name);
497             my $ld2 = DJabberd::Delivery::LocalVHosts->new(allowvhost => $old_vhost->server_name);
498             $old_vhost->add_plugin($ld1);
499             $vhost->add_plugin($ld2);
500              
501             next;
502             }
503             if ($line =~ m!!i) {
504             die "Extraneous subdomain end\n" unless @vhost_stack;
505             $self->add_vhost($vhost);
506             $vhost = pop @vhost_stack;
507             next;
508             }
509             my $close_plugin = sub {
510             die "Can't end a not-open plugin\n" unless $plugin;
511             $plugin->finalize;
512             $vhost->add_plugin($plugin);
513             $plugin = undef;
514             };
515              
516             if ($line =~ //i) {
517             my $class = $1;
518             my $immediate_close = $2;
519             die "Can't configure a plugin outside of a vhost config vhost\n" unless $vhost;
520             die "Can't configure a plugin inside of a plugin\n" if $plugin;
521              
522             my $loaded = eval "use $class; 1;";
523             die "Failed to load plugin $class: $@" if $@;
524             $plugin = $class->new;
525             $close_plugin->() if $immediate_close;
526             next;
527             }
528             if ($line =~ m!!i) {
529             $close_plugin->();
530             next;
531             }
532              
533             die "Syntax error: '$line'\n";
534             };
535             if ($@) {
536             die "Configuration error on line $linenum: $@\n";
537             }
538             }
539             }
540              
541             sub daemonize {
542             my($pid, $sess_id, $i);
543              
544             ## Fork and exit parent
545             if ($pid = fork) { exit 0; }
546              
547             ## Detach ourselves from the terminal
548             Carp::croak("Cannot detach from controlling terminal")
549             unless $sess_id = POSIX::setsid();
550              
551             ## Prevent possibility of acquiring a controling terminal
552             $SIG{'HUP'} = 'IGNORE';
553             if ($pid = fork) { exit 0; }
554              
555             ## Change working directory
556             chdir "/";
557              
558             ## Clear file creation mask
559             umask 0;
560              
561             ## Close open file descriptors
562             close(STDIN);
563             close(STDOUT);
564             close(STDERR);
565              
566             ## Reopen stderr, stdout, stdin to /dev/null
567             open(STDIN, "+>/dev/null");
568             open(STDOUT, "+>&STDIN");
569             open(STDERR, "+>&STDIN");
570             }
571              
572             # Local Variables:
573             # mode: perl
574             # c-basic-indent: 4
575             # indent-tabs-mode: nil
576             # End:
577              
578             1;
579              
580             __END__