source: box/trunk/contrib/bbadmin/accounts.cgi @ 2376

Revision 2376, 13.2 KB checked in by chris, 4 years ago (diff)

Additional debugging for value format error reported by Scott McNee?.

  • Property svn:executable set to *
Line 
1#!/usr/bin/perl
2
3# Box Backup web management interface (c) Chris Wilson, 2008
4#
5# LICENSE: The Box Backup license applies to this code, with the following
6# additional conditions:
7#
8# If you make any changes to this code, except for changes to existing
9# variables in the Configuration section below, you must publish the changes
10# under the same license, whether or not you distribute copies of the
11# changed version.
12#
13# If you use any of this code in a derivative work, you must publish the
14# source code of the derivative work under the same or compatible license,
15# whether or not you distribute copies of the derivative work.
16#
17# The terms of the Box Backup license may be viewed here:
18# https://www.boxbackup.org/license.html
19#
20# If you require access to the code under a different license, this may
21# be negotiated with the copyright holder.
22
23use strict;
24use warnings;
25
26# Variables which you may need to change to match your installation
27# Changes to existing variables are NOT required to be published.
28
29my $box_dir = "/etc/box";
30my $bbstored_dir = "$box_dir/bbstored";
31my $ca_dir = "/mnt/backup/boxbackup/ca";
32
33# You should not need to change these unless you have a non-standard installation
34
35my $bbstored_conf_file = "$box_dir/bbstored.conf";
36my $bbstoreaccounts = "/usr/local/sbin/bbstoreaccounts";
37my $accounts_db_file = undef;
38# my $accounts_db_file = "/etc/box/bbstored/accounts.txt";
39my $raidfile_conf_file = undef;
40# my $raidfile_conf_file = "/etc/box/raidfile.conf";
41my $sign_period = '5000';
42
43# install Perl module with:
44# perl -MCPAN -e 'install Config::Scoped'
45# perl -MCPAN -e 'force install P/PT/PTHOMSEN/BoxBackup/BBConfig-0.03.tar.gz'
46# perl -MCPAN -e 'install Convert::ASN1'
47# download http://search.cpan.org/CPAN/authors/id/L/LE/LEO/Convert-X509-0.1.tar.gz,
48# unpack, and move the Convert folder to /usr/lib/perl5/site_perl/X.X.X
49
50# Check that SSL is being used.
51# DO NOT DISABLE THIS unless you really know what you're doing!
52die "This script requires an SSL web server" unless $ENV{HTTPS};
53
54# Check that the script is protected by basic authentication.
55# DO NOT DISABLE THIS unless you really know what you're doing!
56die "This script requires HTTP Authentication" unless $ENV{REMOTE_USER};
57
58# You should not need to change anything below this line.
59# If you do, you must publish your changes to comply with the license.
60
61use BoxBackup::Config::Accounts;
62use BoxBackup::Config::DiskSets;
63use CGI::Carp qw(fatalsToBrowser);
64use CGI::Pretty;
65use Config::Scoped;
66use Convert::X509::Request;
67use English;
68use Fcntl;
69use File::Temp;
70use URI;
71use URI::QueryParam;
72
73sub check_access($$)
74{
75        my ($file,$desc) = @_;
76        unless (-r $file)
77        {
78                open FILE, "< $file" and die "should have failed";
79                die "Failed to access $desc ($file): $!";
80        }
81}
82
83sub check_executable($$)
84{
85        my ($file,$desc) = @_;
86        unless (-x $file)
87        {
88                open FILE, "< $file" and die "should have failed";
89                die "$desc is not executable ($file): $!";
90        }
91}
92
93
94my $cgi = new CGI;
95
96if (my $download = $cgi->param("download"))
97{
98        my ($filename, $acct_no);
99
100        if ($download eq "cert")
101        {
102                $acct_no = $cgi->param("account");
103                $acct_no =~ tr/0-9a-fA-F//cd;
104                $filename = "$acct_no-cert.pem";
105        }
106        elsif ($download eq "cacert")
107        {
108                $filename = "serverCA.pem";
109        }
110        else
111        {
112                die "No such download method $download";
113        }
114               
115        print $cgi->header(-type => "text/plain",
116                -"content-disposition" => "attachment; filename=$filename");
117       
118        my $send_file;
119
120        if ($download eq "cert")
121        {
122                $send_file = "$ca_dir/clients/$filename";
123        }
124        elsif ($download eq "cacert")
125        {
126                $send_file = "$ca_dir/roots/serverCA.pem";
127        }
128
129        die "File does not exist: $send_file"
130                unless -f $send_file;
131        die "File is not readable by user " . getpwuid($UID) .
132                ": $send_file" unless -r $send_file;
133       
134        open SENDFILE, "< $send_file" or die "Failed to open file " .
135                "$send_file: $!";
136        while (my $line = <SENDFILE>)
137        {
138                print $line;
139        }
140        close SENDFILE;
141        exit 0;
142}       
143
144print $cgi->header(), $cgi->start_html(-title=>"Box Backup Certificates",
145        -style=>'bb.css');
146print $cgi->h1("Box Backup Certificates");
147
148check_access($bbstored_conf_file, "BBStoreD configuration file");
149
150my $bbstored_conf = Config::Scoped->new(file => $bbstored_conf_file)->parse();
151
152$accounts_db_file ||= $bbstored_conf->{'Server'}{'AccountDatabase'};
153die "Missing AccountDatabase in $bbstored_conf_file" unless $accounts_db_file;
154check_access($accounts_db_file, "Accounts Database");
155
156$raidfile_conf_file ||= $bbstored_conf->{'Server'}{'RaidFileConf'};
157die "Missing RaidFileConf in $bbstored_conf_file" unless $raidfile_conf_file;
158check_access($raidfile_conf_file, "RaidFile configuration file");
159
160my $accounts_db = BoxBackup::Config::Accounts->new($accounts_db_file);
161
162check_executable($bbstoreaccounts, "bbstoreaccounts program");
163
164sub error($)
165{
166        my ($message) = @_;
167        unless ($message =~ /^</)
168        {
169                $message = $cgi->p($message);
170        }
171        print $cgi->div({-class=>"error"}, $message);
172        return 0;
173}
174
175sub url
176{
177        my $cgi = shift @_;
178        my %params = @_;
179        my $uri = URI->new($cgi->url(-absolute=>1));
180        foreach my $param (keys %params)
181        {
182                $uri->query_param($param, $params{$param});
183        }
184        return $uri;
185}
186
187sub create_account($)
188{
189        my ($cgi) = @_;
190
191        my $upload = $cgi->upload('req');
192        unless ($upload)
193        {
194                return error("Please attach a certificate request file.");
195        }
196
197        my $tempfile = File::Temp->new("bbaccount-certreq-XXXXXX.pem");
198        my $csr_data = "";
199
200        while (my $line = <$upload>)
201        {
202                print $tempfile $line;
203                $csr_data .= $line;
204        }
205
206        my @accounts = $accounts_db->getAccountIDs();
207        my $new_acc_no = $cgi->param('account');
208        if (not $new_acc_no)
209        {
210                return error("Please enter an account number.");
211        }
212
213        foreach my $account_no (@accounts)
214        {
215                if ($account_no == $new_acc_no)
216                {
217                        return error("The account number $new_acc_no " .
218                                "already exists, please use one which " .
219                                "does not.");
220                }
221        }
222
223        my $req = Convert::X509::Request->new($csr_data);
224        my $cn;
225        foreach my $part ($req->subject)
226        {
227                if ($part =~ /^cn=(.*)/i)
228                {
229                        $cn = $1;
230                        last;
231                }
232        }
233
234        unless ($cn)
235        {
236                return error("The certificate request does not include a " .
237                        "common name, which should be BACKUP-$new_acc_no.");
238        }
239
240        unless ($cn eq "BACKUP-$new_acc_no")
241        {
242                return error("The certificate request includes the wrong " .
243                        "common name. Expected " .
244                        "<tt>BACKUP-$new_acc_no</tt> but found " .
245                        "<tt>$cn</tt>.");
246        }
247
248        my $out_cert_dir = "$ca_dir/clients";
249        unless (-w $out_cert_dir)
250        {
251                return error("Cannot write to certificate directory " .
252                        "<tt>$out_cert_dir</tt> as user " .
253                        "<tt>" . getpwuid($UID) . "</tt>.");
254        }
255
256        my $out_cert = "$out_cert_dir/$new_acc_no-cert.pem";
257        if (-f $out_cert and not -w $out_cert)
258        {
259                return error("The certificate file <tt>$out_cert</tt> " .
260                        "exists and is not writable as user " .
261                        "<tt>$out_cert_dir</tt> as user " .
262                        "<tt>" . getpwuid($UID) . "</tt>.");
263        }
264
265        my $client_ca_cert_file = "$ca_dir/roots/clientCA.pem";
266        unless (-r $client_ca_cert_file)
267        {
268                return error("The client CA certificate file " .
269                        "<tt>$client_ca_cert_file</tt> " .
270                        "is not readable by user " .
271                        "<tt>" . getpwuid($UID) . "</tt>.");
272        }
273
274        my $client_ca_key_file = "$ca_dir/keys/clientRootKey.pem";
275        unless (-r $client_ca_key_file)
276        {
277                return error("The client CA key file " .
278                        "<tt>$client_ca_key_file</tt> " .
279                        "is not readable by user " .
280                        "<tt>" . getpwuid($UID) . "</tt>.");
281        }
282
283        my $serial_file = "$ca_dir/roots/clientCA.srl";
284        unless (-w $serial_file)
285        {
286                return error("The certificate serial number file " .
287                        "<tt>$serial_file</tt> " .
288                        "is not writable by user " .
289                        "<tt>" . getpwuid($UID) . "</tt>.");
290        }
291
292        my $outputfile = File::Temp->new("bbaccounts-openssl-output-XXXXXX");
293
294        if (system("openssl x509 -req -in $tempfile -sha1 " .
295                "-extensions usr_crt " .
296                "-CA $client_ca_cert_file " .
297                "-CAkey $client_ca_key_file " .
298                "-out $out_cert -days $sign_period " .
299                ">$outputfile 2>&1") != 0)
300        {
301                open ERR, "< $outputfile" or die "$outputfile: $!";
302                my $errors = join("", <ERR>);
303                close ERR;
304                return error($cgi->p("Failed to sign certificate:") .
305                        $cgi->pre($errors));
306        }
307       
308        my $cert_uri = url($cgi, download => "cert", account => $new_acc_no);
309        my $ca_uri   = url($cgi, download => "cacert");
310
311        print $cgi->div({-class=>"success"},
312                $cgi->p("Account created. Please download the following " .
313                        "files:") .
314                $cgi->ul(
315                        $cgi->li($cgi->a({href=>$cert_uri},
316                                "Client Certificate")),
317                        $cgi->li($cgi->a({href=>$ca_uri},
318                                "CA Certificate"))
319                        )
320                );
321
322        return 1;
323}
324
325if ($cgi->param("create"))
326{
327        print $cgi->h2("Account Creation");
328        create_account($cgi);
329}
330
331print $cgi->h2("Accounts");
332print $cgi->start_table({-border=>0, -class=>"numbers"});
333
334print $cgi->Tr(
335        $cgi->th("Account"),
336        $cgi->th('Used'),               $cgi->th('%'),
337        $cgi->th('Old files'),          $cgi->th('%'),
338        $cgi->th('Deleted files'),      $cgi->th('%'),
339        $cgi->th('Directories'),        $cgi->th('%'),
340        $cgi->th('Soft limit'),         $cgi->th('%'),
341        $cgi->th('Hard limit'),
342        $cgi->th('Actions')
343        );
344
345sub human_format($)
346{
347        my ($kb) = @_;
348        die "bad format in value: expected number followed by kB, " .
349                "found '$kb'" unless $kb =~ /^(\d+) (kB)$/;
350
351        my $value = $1;
352        my $units = $2;
353
354        if ($value > 1024)
355        {
356                $value /= 1024;
357                $units = "MB";
358        }
359
360        if ($value > 1024)
361        {
362                $value /= 1024;
363                $units = "GB";
364        }
365
366        $value = sprintf("%.1f", $value);
367        return "$value $units";
368}
369
370sub bbstoreaccounts_format($)
371{
372        my ($kb) = @_;
373        die unless $kb =~ /^(\d+) (kB)$/;
374
375        my $value = $1;
376        my $units = "K";
377
378        unless ($value % 1024)
379        {
380                $value /= 1024;
381                $units = "M";
382        }
383
384        unless ($value % 1024)
385        {
386                $value /= 1024;
387                $units = "G";
388        }
389
390        return "$value$units";
391}
392
393sub get_account_info($)
394{
395        my ($account) = @_;
396
397        open BBSA, "$bbstoreaccounts -c $bbstored_conf_file -m info $account |"
398                or die "Failed to get account info for $account: $!";
399
400        my $account_info = {};
401
402        while (my $line = <BBSA>)
403        {
404                unless ($line =~ m/([^:]*): (.*)/)
405                {
406                        die "Bad format in bbstoreaccounts info output " .
407                                "for account $account: '$line'";
408                }
409
410                my $name = $1;
411                my $value = $2;
412
413                if ($value =~ /(.*), (.*)/)
414                {
415                        $account_info->{$name} = [$1, $2];
416                }
417                else
418                {
419                        $account_info->{$name} = $value;
420                }
421        }
422
423        return $account_info;
424}
425
426sub format_account_info($)
427{
428        my ($values) = @_;
429        my $kb = $values->[0];
430        my $pc = $values->[1];
431        return $cgi->td(human_format($kb)), $cgi->td($values->[1]);
432}
433
434my %account_numbers;
435
436my @accounts = $accounts_db->getAccountIDs();
437foreach my $i (@accounts)
438{
439        die "Duplicate account number $i" if $account_numbers{hex($i)};
440        $account_numbers{hex($i)} = 1;
441
442        # Find out what account is on what diskset.
443        my $disk = $accounts_db->getDisk($i);
444
445        # store limits
446        my $account_info = get_account_info($i);
447
448        print $cgi->Tr(
449                $cgi->td($i),
450                format_account_info($account_info->{'Used'}),
451                format_account_info($account_info->{'Old files'}),
452                format_account_info($account_info->{'Deleted files'}),
453                format_account_info($account_info->{'Directories'}),
454                format_account_info($account_info->{'Soft limit'}),
455                $cgi->td(human_format($account_info->{'Hard limit'}[0])),
456                $cgi->td($cgi->a({-href=>url($cgi, account=>$i)},
457                        "Edit"))
458                );
459}
460
461print $cgi->end_table();
462
463my $account_no = $cgi->param("account");
464$account_no =~ tr/0-9a-fA-F//cd;
465
466if (not $cgi->param("showcreate"))
467{
468        print $cgi->start_form,
469                $cgi->submit(-name=>"showcreate",
470                -value=>"Create Account"),
471                $cgi->end_form();
472}
473
474if ($account_no)
475{
476        print $cgi->h2("Edit Account");
477        my $account_info = get_account_info($account_no);
478        $cgi->param("account", $account_no);
479        $cgi->param("soft_limit",
480                bbstoreaccounts_format($account_info->{'Soft limit'}[0]));
481        $cgi->param("hard_limit",
482                bbstoreaccounts_format($account_info->{'Hard limit'}[0]));
483}
484elsif ($cgi->param("showcreate"))
485{
486        print $cgi->h2("Create Account");
487}
488
489if ($account_no or $cgi->param("showcreate"))
490{
491        my $new_account_no = 1;
492        while ($account_numbers{$new_account_no})
493        {
494                $new_account_no++;
495        }
496
497        my $disksets_conf = BoxBackup::Config::DiskSets->new($raidfile_conf_file);
498        my @disk_names = $disksets_conf->getListofDisks();
499        my @disk_numbers;
500        my %disk_labels;
501
502        foreach my $name (@disk_names)
503        {
504                my $num = $disksets_conf->getParamVal($name, "SetNumber");
505                push @disk_numbers, $num;
506                $disk_labels{$num} = $name;
507        }
508
509        print $cgi->start_multipart_form(),
510                $cgi->start_table();
511
512        if ($account_no)
513        {
514                print $cgi->Tr(
515                        $cgi->th("Account Number"),
516                        $cgi->td($account_no . 
517                                $cgi->hidden("account", $account_no))
518                        );
519        }
520        else
521        {
522                print $cgi->Tr(
523                        $cgi->th("Account Number"),
524                        $account_no ? $account_no :
525                        $cgi->td($cgi->textfield(-name => "account",
526                                -default => sprintf("%x", $new_account_no))),
527                        );
528        }
529
530        if (not $account_no)
531        {
532                print $cgi->Tr(
533                        $cgi->th("Disk Set"),
534                        $cgi->td($cgi->popup_menu(-name => "disk_set",
535                                -values => \@disk_numbers,
536                                -labels => \%disk_labels))
537                        );
538        }
539
540        print   $cgi->Tr(
541                        $cgi->th("Soft Limit"),
542                        $cgi->td($cgi->textfield(-name => "soft_limit",
543                                -default => "10G"))
544                        ),
545                $cgi->Tr(
546                        $cgi->th("Hard Limit"),
547                        $cgi->td($cgi->textfield(-name => "hard_limit",
548                                -default => "20G"))
549                        ),
550                $cgi->Tr(
551                        $cgi->th("Certificate Request"),
552                        $cgi->td($cgi->filefield({
553                                -name => "req",
554                                -default => "*.crt"
555                                }))
556                        );
557
558        if ($account_no)
559        {
560                print $cgi->Tr(
561                        $cgi->th(),
562                        $cgi->td($cgi->submit(-name => "update",
563                                -value => "Update Account"))
564                        );
565        }
566        else
567        {
568                print $cgi->Tr(
569                        $cgi->th(),
570                        $cgi->td($cgi->submit(-name => "create",
571                                -value => "Create Account"))
572                        );
573        }
574
575        print $cgi->end_table(), $cgi->end_form();
576}
577
578print $cgi->end_html;
579
580exit 0;
Note: See TracBrowser for help on using the repository browser.