1#!/usr/local/bin/perl -s
2##
3## Razor2::Client:Config
4##
5## Copyright (c) 2002, Vipul Ved Prakash.  All rights reserved.
6## This code is free software; you can redistribute it and/or modify
7## it under the same terms as Perl itself.
8##
9## $Id: Config.pm,v 1.66 2007/05/10 20:32:10 rsoderberg Exp $
10
11package Razor2::Client::Config;
12use strict;
13use Data::Dumper;
14use vars qw( $VERSION );
15use File::Copy;
16use File::Spec;
17
18use Razor2::Logger;
19
20#use base qw(Razor2::Logger);
21
22sub new {
23    my ($class) = @_;
24    return bless {}, $class;
25}
26
27#
28# figures out razorhome and razorconf file
29#
30sub read_conf {
31    my ($self,$params) = @_;
32
33    my $default_conf_fn = "$self->{global_razorhome}/razor-agent.conf";
34    my $conf;
35    my $defaults = $self->default_agent_conf();
36    my $use_engines = $defaults->{use_engines};
37
38    if ($self->{razorconf}) {
39        #
40        # cmd-line config file specified
41        #
42        $conf = $self->read_file($self->{razorconf},$defaults)
43            unless ($self->{opt}->{create} && $self->{opt}->{config});
44        if ($self->{opt}->{razorhome}) {
45            $self->{computed_razorhome} = $self->{razorhome} = $self->{opt}->{razorhome};
46        } else {
47            $self->find_home($self->{opt}->{razorhome} || $conf->{razorhome});
48        }
49    } else {
50
51        $self->compute_razorconf();
52
53        if ($self->{razorconf}) {
54            $conf = $self->read_file($self->{razorconf},$defaults);
55        } else {
56            $self->log(6, "No razor-agent.conf found, using defaults. ");
57            $conf = $defaults;
58        }
59    }
60
61    foreach (keys %{$defaults}) {
62        next if exists $conf->{$_};
63        $conf->{$_} = $defaults->{$_};
64    }
65
66    # Override use_engines from defaults. To store use_engines
67    # in the config file is a design flaw, since the client
68    # supported engines are defined by the razor-agents source,
69    # and could potentially be incorrect in the config file
70    # after an upgrade.
71
72    $conf->{use_engines} = $use_engines;
73
74    foreach (keys %{$self->{opt}}) {
75        next if ($_ eq '' || $_ eq 'use_engines' || $_ eq 'razorzone');
76        $conf->{$_} = $self->{opt}->{$_};
77    }
78
79    if ($params) {
80        foreach (keys %$params) {
81            next if ($_ eq '' || $_ eq 'use_engines' || $_ eq 'razorzone');
82            $conf->{$_} = $params->{$_};
83        }
84    }
85
86    $self->{conf} = $conf;
87
88    #
89    # post config processing
90    # insert things that should not be in conf here
91    #
92
93    # turn off run-time warnings unless debug flag passed
94    # http://www.perldoc.com/perl5.6.1/pod/perllexwarn.html
95    $^W = 0 unless $conf->{debug};
96
97    # add full path to all config values that need them
98    #
99    if ($self->{razorhome}) {
100    foreach (qw( logfile pidfile listfile_catalogue listfile_nomination
101                 listfile_discovery whitelist identity)) {
102        next unless $conf->{$_};
103        next if $conf->{$_} =~ /^\//;
104        next if ($_ eq 'logfile' && ($conf->{$_} eq 'syslog' || $conf->{$_} eq 'sys-syslog'));
105        $conf->{$_} = "$self->{razorhome}/$conf->{$_}";
106    }
107    }
108    return $self->{conf};
109}
110
111#
112#  Figure out which conf to use - user's own, or system conf.
113#
114#  If no user conf or no system conf, razorconf will be blank
115#  but computed_razorconf will be set.
116#
117#  However, if razorhome is still unknown, computed_razorconf can be blank
118#
119sub compute_razorconf {
120    my $self = shift;
121
122    my $default_conf_fn = "$self->{global_razorhome}/razor-agent.conf";
123
124    $self->{razorconf} = "";
125    $self->find_home();
126    if ($self->{razorhome}) {
127        my $mycf = "$self->{razorhome}/razor-agent.conf";
128        $self->{computed_razorconf} = $mycf;
129        if (-r $mycf) {
130            $self->{razorconf} = $mycf;
131        } elsif (-e $mycf) {
132            $self->log(5, "Found but can't read $mycf, skipping.");
133        } else {
134            $self->log(5, "No $mycf found, skipping.");
135        }
136    }
137    if (!$self->{razorconf} && -e $default_conf_fn) {
138        if (-r $default_conf_fn) {
139            $self->{razorconf} = $default_conf_fn;
140        } else {
141            $self->log(5, "Found but can't read $default_conf_fn, skipping.");
142            $self->{computed_razorconf} ||= $default_conf_fn;
143        }
144    }
145}
146
147sub write_conf {
148    my ($self,$hash)  = @_;
149
150    unless ($self->{razorconf}) {
151        $self->log(5,"Cannot write_conf without razorconf set");
152        return $self->error("Cannot write_conf without razorconf set");
153    }
154    my $now = localtime();
155    my $srcmsg;
156    unless ($hash) {
157        $hash = $self->default_agent_conf();
158        if (-r $self->{razorconf}) {
159            $hash = $self->read_file( $self->{razorconf}, $hash);
160            $srcmsg = "Non-default values taken from $self->{razorconf}";
161        } else {
162            $srcmsg = "Created with all default values";
163        }
164    }
165
166    my $clientheader = <<EOFCLIENT;
167#
168# Razor2 config file
169#
170# Autogenerated by $self->{name_version}
171# $now
172# $srcmsg
173#
174# see razor-agent.conf(5) man page
175#
176EOFCLIENT
177    return $self->write_file($self->{razorconf}, $hash, 0, $clientheader);
178}
179
180
181sub find_user {
182    my $self = shift;
183
184    return 1 if $self->{user};
185
186    $self->{user} = getpwuid($>) || do {
187        $self->log(1, "Can't figure out who the effective user is: $!");
188        return undef;
189    };
190    return 1;
191}
192
193# compute razorhome.  like so:
194#
195#    -home=/tmp/razor/              used if readable, else
196#    'razorhome' from config file   used if readable, else
197#    <home>/.razor/                 used if readable, else
198#    <home>/.razor/                 is created.  if that fails, no razorhome.
199#    -conf=/foo/razor/razor.conf    if all else fails pick it up from the config file path,
200#                                   if one is available
201
202sub find_home {
203    my ($self,$rhome) = @_;
204
205    my $dotrazor = '.razor';
206       $dotrazor = '_razor' if $^O eq 'VMS';
207
208    if (defined $self->{razorhome}) {
209        $self->{razorhome_computed} = $self->{razorhome};
210        return 1;
211    }
212
213    if (defined $self->{opt}->{razorhome}) {
214        $self->{razorhome_computed} = $self->{razorhome};
215        return 1;
216    }
217
218    # if razorhome is read from config file, its passed as rhome
219    unless ($rhome) {
220
221        if (defined $ENV{HOME}) {
222            $rhome = File::Spec->catdir("$ENV{HOME}", "$dotrazor");
223        } else {
224            return unless $self->find_user();
225            $rhome = File::Spec->catdir((getpwnam($self->{user}))[7], "$dotrazor") || "/home/$self->{user}/$dotrazor";
226        }
227        $rhome = VMS::Filespec::unixify($rhome) if $^O eq 'VMS';
228        $self->log(8,"Computed razorhome from env: $rhome");
229    }
230    $self->{razorhome_computed} = $rhome;
231
232    if (-d $rhome) {
233        if (-w $rhome) {
234            $self->log(6,"Found razorhome: $rhome");
235        } else {
236            $self->log(6,"Found razorhome: $rhome, however, can't write to it.");
237        }
238        $self->{razorhome} = $rhome;
239        return 1;
240
241    }
242
243    if ($self->{razorconf}) {
244        my $path = $$self{razorconf};
245        if ($path =~ m:/:) {
246            if ($path =~ m:(.*)/:) {
247                $self->{razorhome} = $1;
248                return 1;
249            }
250        }
251    }
252
253    $self->log(5,"No razorhome found, using all defaults");
254    $self->{razorhome} = "";
255    return 1;
256}
257
258sub create_home {
259    my ($self,$rhome) = @_;
260
261    if (-d $rhome) {
262        $self->{razorhome} = $rhome;
263        return 1;
264    }
265    if (mkdir $rhome, 0755) {
266        $self->log(6,"Created razorhome: $rhome");
267        $self->{razorhome} = $rhome;
268        return 1;
269    }
270    return $self->error("Could not mkdir $rhome: $!");
271}
272
273sub compute_identity {
274    my ($self) = @_;
275    $self->find_home() or return;
276
277    return 1 if $self->{identity};
278
279    my $id;
280
281    if ($id = $self->{opt}->{identity}) {
282        $self->{identity} = $self->my_readlink($id);
283        # warn we can't read it unless we are registering new identity
284        $self->log(6,"Can't read identity:  $self->{identity}")
285            unless ($self->{opt}->{register}) || (-r $self->{identity});
286        return 1;
287
288    # if not specified via cmd-line, just compute it, don't read it.
289
290    } elsif ($id = $self->{conf}->{identity}) {
291        $self->{identity} = $self->my_readlink($id);
292        return 1;
293
294    } else {
295        $id = $self->{razorhome} ? "$self->{razorhome}/identity" : "";
296        $self->{identity} = $self->my_readlink($id);
297        return 1;
298    }
299}
300
301
302sub get_ident {
303    my ($self) = @_;
304    $self->find_home() or return;
305
306    my $idfn = $self->{identity};
307    return $self->error("Cannot read the identity file: $idfn") unless -r $idfn;
308
309    $idfn = $self->my_readlink($idfn);
310
311    my $mode = ((stat($idfn))[2]) & 07777; # mask off file type
312    if ($mode & 0007) {
313        $self->log(2,"Please chmod $idfn so it is not world readable.");
314    }
315    return $self->read_file( $idfn );
316}
317
318# returns { user => $user, pass => $pass } if success
319# returns 2 if error
320sub register_identity {
321    my($self, $user, $pass) = @_;
322    my $ident = $self->register({
323                        user => $user,
324                        pass => $pass,
325                            });
326    $self->disconnect()         or return 2;
327    return $ident || 2;
328}
329
330sub ident_fn {
331    my ($self,$ident) = @_;
332    $self->find_home() or return;
333
334    my $orig;
335    my $syml;
336    my $obase = "identity-$ident->{user}";
337
338    $obase = $1 if $obase =~ /^(\S+)$/; # untaint obase
339
340    # if it's a user specified identity file, don't symlink
341    unless ($orig = $self->{opt}->{identity}) {
342        $orig  = "$self->{razorhome}/$obase";
343        $syml  = "$self->{razorhome}/identity";
344
345        $orig = $1 if $orig =~ /^(\S+)$/; # untaint orig
346        $syml = $1 if $syml =~ /^(\S+)$/; # untaint syml
347    }
348
349
350    return ($orig, $obase, $syml);
351}
352
353sub save_ident {
354    my ($self,$ident) = @_;
355
356    my ($orig, $obase, $syml) = $self->ident_fn($ident);
357
358    unless (length $orig) {
359        return $self->error("couldn't figure out identity filename");
360    }
361
362    rename($orig,"$orig.bak") if -s $orig;
363    my $umask = umask 0077;  # disable group and all from read/write/execute
364    $self->write_file($orig,$ident) or return;
365    umask $umask;
366
367    # don't create a symlink if user specified identity file from cmd-line
368    return $orig unless $syml;
369
370    unless ($self->{opt}->{symlink}) {
371        return $orig if -e $syml;  # already has another identity
372    }
373
374    unlink $syml;
375    if (eval { symlink("",""); 1 } ) {
376        $obase = $1 if $obase =~ /^(\S+)$/; # untaint obase
377        $syml = $1 if $syml =~ /^(\S+)$/; # untaint syml
378
379        symlink $obase, $syml or
380            return $self->error("Created $orig, but could not symlink to it $syml: $!");
381    } else {
382        $self->log(5, "symlinks don't work on this machine");
383        copy($orig,$syml);
384    }
385    return $orig;
386}
387
388sub my_readlink {
389    my ($self,$fn) = @_;
390
391    while (1) {
392        return $fn unless -l $fn;
393
394        if ($fn =~ /^(.*)\/([^\/]+)$/) {
395            my $dir = $1;
396            $fn = readlink $fn;
397            $fn = $1 if $fn =~ /^(\S+)$/; # untaint readlink
398            $fn = "$dir/$fn" unless $fn =~ /^\//;
399        } else {
400            $fn = readlink $fn;
401            $fn = $1 if $fn =~ /^(\S+)$/; # untaint readlink
402        }
403    }
404}
405
406sub parse_value {
407    my ($self, $value) = @_;
408
409    $value =~ s/^\s+//;
410    $value =~ s/\s+$//;
411    if ($value =~ m:,:) {
412        my @values = split /,\s*/, $value;
413        return [@values];
414    } else {
415        return $value;
416    }
417}
418
419# given filename, returns hash ref of key = val from file
420# if $nothash, than no key && val, just return array ref containing all lines.
421#
422sub read_file {
423    my ($self,$fn,$h,$nothash) = @_;
424
425    unless (defined $fn && length $fn) {
426        $self->log(5,"Filename not provided to read_file");
427        return;
428    }
429
430    my $conf = ref($h) eq 'HASH' ? $h : {};
431
432    if( $^O eq 'VMS' && $fn !~ /\[/ ) {
433        my ($dir,$file,$ext) = ($fn =~ /(^.*\/)(.*)(\..*)$/);
434        $dir =~ s/\./_/g;
435        $file =~ s/\./_/g;
436        $fn = $dir . $file . $ext;
437    }
438
439    $fn = $1 if $fn =~ /^(\S+)$/; # untaint $fn
440
441    unless (defined($fn) && (($fn =~ /^\//) || -e $fn)) {
442        $self->log(7,"Can't read file $fn, looking relative to $self->{razorhome}");
443        $fn = "$self->{razorhome}/$fn";
444        $fn = $1 if $fn =~ /^(\S+)$/; # untaint $fn
445    }
446
447    my $total = 0;
448    my @lines;
449    unless (open CONF, "<$fn") {
450        $self->log(5,"Can't read file $fn: $!");
451        return;
452    }
453
454    # set $/ to the default in case someone has overwritten $/ elsewhere
455    local $/ = "\n";
456
457    for (<CONF>) {
458        chomp;
459        next if /^\s*#/;
460        if ($nothash) {
461            next unless s/^\s*(.+?)\s*$/$1/; # untaint
462            $conf->{$_} = 7;
463            push @lines, $_;
464        } else {
465            next unless /=/;
466            my ($attribute, $value) = /^\s*(.+?)\s*=\s*(.+?)\s*$/; # untaint
467            next unless (defined $attribute && defined $value);
468            $conf->{$attribute} = $self->parse_value($value);
469        }
470        $total++;
471    }
472    close CONF;
473    $self->log(5, "read_file: $total items read from $fn");
474
475    return $nothash ? \@lines : $conf;
476}
477
478# given hash ref, writes to file key = val
479# NOTE: key should not contain '=';
480#
481# given array ref, writes to file each item
482#
483# given scalar ref, writes to file
484#
485sub write_file {
486    my ($self,$fn,$hash,$append,$header,$lock)  = @_;
487
488    $fn = "$self->{razorhome}/$fn" unless ($fn =~ /^\//);
489    $fn = ">$fn" if $append;
490
491    if( $^O eq 'VMS' && $fn !~ /\[/ ) {
492        my ($dir,$file,$ext) = ($fn =~ /(^.*\/)(.*)(\..*)$/);
493        $dir =~ s/\./_/g;
494        $file =~ s/\./_/g;
495        $fn = $dir . $file . $ext;
496    }
497
498    $fn = $1 if $fn =~ /^(\S+)$/; # untaint $fn
499
500    # check for lock file
501    my $lockfile = "$fn.lock";
502    $lockfile = "${fn}_lock;1" if $^O eq 'VMS';
503    if ($lock) {
504        if (-r "$lockfile") {
505            return $self->error("File is locked, try again later: $lockfile");
506        } else {
507            unless (open LOCK, ">$fn.lock") {
508                return $self->error("Can't create lock file $fn.lock: $!");
509            }
510            close LOCK;
511        }
512    }
513    unless (open CONF, ">$fn") {
514        return $self->error("Can't write file $fn: $!");
515    }
516    print CONF "$header\n" if $header;
517    my $total = 0;
518    if (ref($hash) eq 'HASH') {
519        foreach (sort keys %$hash) {
520            return $self->error("Key cannot contain '=': $_") if /=/;
521            printf CONF "%-22s = ", $_;
522            if (ref($hash->{$_}) eq "ARRAY") {
523                print CONF join(',', @{$hash->{$_}}) ."\n";
524            } else {
525                print CONF $hash->{$_} ."\n";
526            }
527            $total++;
528        }
529
530    } elsif (ref($hash) eq 'ARRAY') {
531        foreach (@$hash) {
532            next unless /\S/;
533            if (ref($_) eq "ARRAY") {
534                print CONF join(', ', @$_) ."\n";
535            } else {
536                print CONF $_ ."\n";
537            }
538            $total++;
539        }
540    } elsif (ref($hash) eq 'SCALAR') {
541        printf CONF $$hash;
542        $total++;
543    }
544    close CONF;
545    if ($lock) {
546        1 while unlink "$lockfile";
547    }
548    $self->log(5, "wrote $total ". ref($hash) ." items to file: $fn");
549
550    #return $total;
551    return 1;
552}
553
554
555
556sub default_server_conf {
557    my $self = shift;
558    my $defaults = {
559        srl   => -1,
560        ep4   => '7542-10',
561        bql   => 4,
562        ac    => 0,
563        bqs   => 128,
564        se    => 'C8',  # engines 4, 8
565        dre   => 4,
566        zone  => 'razor2.cloudmark.com',
567        logic_method => 4,
568    };
569
570    # split strings with , into array
571    foreach (keys %$defaults) {
572        $defaults->{$_} = $self->parse_value($defaults->{$_});
573    }
574    return $defaults;
575}
576
577
578sub default_agent_conf {
579    my $self = shift;
580    #
581    # These get overwritten by whatever's in config file,
582    # which in turn gets overwritten by cmd-line options.
583    #
584    my $defaults = {
585        debuglevel         => "3",
586        logfile            => "razor-agent.log",
587        listfile_catalogue => "servers.catalogue.lst",
588        listfile_nomination => "servers.nomination.lst",
589        listfile_discovery => "servers.discovery.lst",
590        min_cf             => "ac",
591        turn_off_discovery => "0",
592        ignorelist         => "0",
593        razordiscovery     => "discovery.razor.cloudmark.com",
594        rediscovery_wait   => "172800",
595        report_headers     => "1",
596        whitelist          => "razor-whitelist",
597        use_engines        => "4, 8",
598        identity           => "identity",
599        logic_method       => 4,
600    };
601
602    # 'razorhome' can exist in .conf, but we compute it instead of listing it here
603    # 'rlimit' ?
604
605    # split strings with , into array
606    foreach (keys %$defaults) {
607        $defaults->{$_} = $self->parse_value($defaults->{$_});
608    }
609    return $defaults;
610}
611
612
6131;
614
615