1# Copyright (c) 2010-2013 Zmanda, Inc. All Rights Reserved. 2# 3# This program is free software; you can redistribute it and/or 4# modify it under the terms of the GNU General Public License 5# as published by the Free Software Foundation; either version 2 6# of the License, or (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, but 9# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 10# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 11# for more details. 12# 13# You should have received a copy of the GNU General Public License along 14# with this program; if not, write to the Free Software Foundation, Inc., 15# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 16# 17# Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300 18# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com 19 20use Test::More tests => 30; 21use strict; 22use warnings; 23 24use Data::Dumper; 25 26use lib "@amperldir@"; 27use Amanda::ClientService; 28use Amanda::Constants; 29use Amanda::Util; 30use Amanda::Debug; 31use Amanda::Config qw( :init ); 32use Amanda::MainLoop; 33use Socket; 34 35config_init(0, undef); 36Amanda::Debug::dbopen('installcheck'); 37 38# test connect_streams 39{ 40 # that these tests assume that DATA_FD_OFFSET and DATA_FD_COUNT have these values 41 is($Amanda::Constants::DATA_FD_OFFSET, 50, "DATA_FD_OFFSET is what I think it is"); 42 is($Amanda::Constants::DATA_FD_COUNT, 3, "DATA_FD_COUNT is what I think it is"); 43 44 sub test_connect_streams { 45 my ($args, $exp_line, $exp_closed_fds, $exp_streams, $msg) = @_; 46 47 my $cs = Amanda::ClientService->new(); 48 $cs->{'_dont_use_real_fds'} = 1; 49 $cs->{'_argv'} = [ 'amandad' ]; 50 my $got_line = $cs->connect_streams(@$args); 51 52 is($got_line, $exp_line, "$msg (CONNECT line)"); 53 54 is_deeply([ sort @{$cs->{'_would_have_closed_fds'}} ], 55 [ sort @$exp_closed_fds ], 56 "$msg (closed fds)"); 57 58 # get the named streams and their fd's 59 my %streams; 60 while (@$args) { 61 my $name = shift @$args; 62 my $dirs = shift @$args; 63 $streams{$name} = [ $cs->rfd($name), $cs->wfd($name) ]; 64 } 65 66 is_deeply(\%streams, $exp_streams, "$msg (streams)"); 67 } 68 69 test_connect_streams( 70 [ 'DATA' => 'rw' ], 71 'CONNECT DATA 50', 72 [ 52, 53, 54, 55 ], 73 { 'DATA' => [ 51, 50 ] }, 74 "simple read/write DATA stream"); 75 76 test_connect_streams( 77 [ 'DATA' => 'r' ], 78 'CONNECT DATA 50', 79 [ 50, 52, 53, 54, 55 ], 80 { 'DATA' => [ 51, -1 ] }, 81 "read-only stream"); 82 83 test_connect_streams( 84 [ 'DATA' => 'w' ], 85 'CONNECT DATA 50', 86 [ 51, 52, 53, 54, 55 ], 87 { 'DATA' => [ -1, 50 ] }, 88 "write-only stream"); 89 90 test_connect_streams( 91 [ 'DATA' => 'rw', 'RD' => 'r', 'WR' => 'w' ], 92 'CONNECT DATA 50 RD 51 WR 52', 93 [ 52, 55 ], 94 { 'DATA' => [ 51, 50 ], 95 'RD' => [ 53, -1 ], 96 'WR' => [ -1, 54 ] }, 97 "three streams"); 98} 99 100# test from_inetd and friends 101{ 102 my $cs; 103 104 $cs = Amanda::ClientService->new(); 105 $cs->{'_argv'} = []; 106 ok($cs->from_inetd, "no argv[0] interpreted as a run from inetd"); 107 108 $cs = Amanda::ClientService->new(); 109 $cs->{'_argv'} = [ 'installcheck' ]; 110 ok($cs->from_inetd, "argv[0] = 'installcheck' also interpreted as a run from inetd"); 111 112 $cs = Amanda::ClientService->new(); 113 $cs->{'_argv'} = [ 'amandad' ]; 114 ok(!$cs->from_inetd, "argv[0] = 'amandad' interpreted as a run from amandad"); 115 116 $cs = Amanda::ClientService->new(); 117 $cs->{'_argv'} = [ 'amandad', 'bsdgre' ]; 118 is($cs->amandad_auth, "bsdgre", 119 "argv[1] = 'bsdgre' interpreted as auth"); 120 121 $cs = Amanda::ClientService->new(); 122 $cs->{'_argv'} = [ 'amandad' ]; 123 is($cs->amandad_auth, undef, 124 "amandad_auth interpreted as undef if missing"); 125} 126 127# test add_connection and half-close operations 128sub test_connections { 129 my ($finished_cb) = @_; 130 131 my $port; 132 my $cs = Amanda::ClientService->new(); 133 $cs->{'_argv'} = [ ]; 134 135 my $steps = define_steps 136 cb_ref => \$finished_cb; 137 138 step listen => sub { 139 $port = $cs->connection_listen('FOO', 0); 140 141 $steps->{'fork'}->(); 142 }; 143 144 step fork => sub { 145 # fork off a child to connect to and write to that port 146 if (fork() == 0) { 147 socket(my $foo, PF_INET, SOCK_STREAM, getprotobyname('tcp')) 148 or die "error creating connect socket: $!"; 149 connect($foo, sockaddr_in($port, inet_aton("127.0.0.1"))) 150 or die "error connecting: $!"; 151 my $info = <$foo>; 152 print $foo "GOT[$info]"; 153 close($foo); 154 exit(0); 155 } else { 156 $steps->{'accept'}->(); 157 } 158 }; 159 160 step accept => sub { 161 $cs->connection_accept('FOO', 90, $steps->{'accept_finished'}); 162 }; 163 164 step accept_finished => sub { 165 # write a message to the fd and read back the result; this is 166 # synchronous 167 my $msg = "HELLO WORLD"; 168 Amanda::Util::full_write($cs->wfd('FOO'), $msg, length($msg)) 169 or die "full write: $!"; 170 $cs->close('FOO', 'w'); 171 is($cs->wfd('FOO'), -1, 172 "FOO is closed for writing"); 173 174 my $input = Amanda::Util::full_read($cs->rfd('FOO'), 1024); 175 $cs->close('FOO', 'r'); 176 is_deeply([ keys %{$cs->{'_streams'}} ], [ 'main' ], 177 "FOO stream is deleted when completely closed"); 178 179 is($input, "GOT[HELLO WORLD]", 180 "both directions of the FOO stream work"); 181 182 $finished_cb->(); 183 }; 184} 185test_connections(\&Amanda::MainLoop::quit); 186Amanda::MainLoop::run(); 187 188# check rfd and wfd 189{ 190 my $cs = Amanda::ClientService->new(); 191 is($cs->rfd('main'), 0, 192 "main rfd is stdin"); 193 is($cs->wfd('main'), 1, 194 "main wfd is stdout"); 195 is($cs->wfd('none'), -1, 196 "wfd returns -1 for invalid stream"); 197 is($cs->rfd('none'), -1, 198 "rfd returns -1 for invalid stream"); 199} 200 201# check check_bsd_security 202{ 203 # note that we can't completely test this, because BSD security entails checking 204 # DNS and privileged ports, neither of which are controllable from the installcheck 205 # environment. However, we can at least call the method. 206 207 my $cs = Amanda::ClientService->new(); 208 $cs->{'_argv'} = [ 'installcheck' ]; # basically neuters check_bsd_security 209 210 ok(!$cs->check_bsd_security('main', "USER bart"), 211 "check_bsd_security returns undef"); 212} 213 214# check parse_req 215{ 216 my $cs = Amanda::ClientService->new(); 217 my $req_str; 218 219 # is_deeply doesn't like objects very much 220 sub strip_features { 221 my ($x) = @_; 222 #use Data::Dumper; 223 #print Dumper($x); 224 return $x unless defined $x->{'features'}; 225 $x->{'features'} = "featureset"; 226 return $x; 227 } 228 229 $req_str = <<ENDREQ; 230OPTIONS auth=passport;features=f0039; 231FOO 232ENDREQ 233 is_deeply(strip_features($cs->parse_req($req_str)), { 234 lines => [ 'OPTIONS auth=passport;features=f0039;', 'FOO' ], 235 options => { 236 auth => 'passport', 237 features => 'f0039', 238 }, 239 errors => [], 240 features => "featureset", 241 }, "parse_req parses a request properly"); 242 243 $req_str = <<ENDREQ; 244OPTIONS auth=bsd;no-features;yes=no; 245ENDREQ 246 is_deeply(strip_features($cs->parse_req($req_str)), { 247 lines => [ 'OPTIONS auth=bsd;no-features;yes=no;' ], 248 options => { 249 auth => 'bsd', 250 yes => 'no', 251 'no-features' => 1, 252 }, 253 errors => [], 254 features => undef, 255 }, "parse_req parses a request with boolean options"); 256 257 $req_str = <<ENDREQ; 258OPTIONS turn=left; 259OPTIONS turn=right; 260ENDREQ 261 is_deeply(strip_features($cs->parse_req($req_str)), { 262 lines => [ 'OPTIONS turn=left;', 'OPTIONS turn=right;' ], 263 options => { 264 turn => 'left', 265 }, 266 errors => [ 'got multiple OPTIONS lines' ], 267 features => undef, 268 }, "parse_req detects multiple OPTIONS lines as an error"); 269} 270