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