#!/usr/bin/perl ####### meta-verify # # System and database integrity check utility # for the Sun Cobalt RaQ4 product family # # Copyright (c) 2000 Cobalt Networks, Inc. # Copyright (c) 2002 Sun Microsystems, Inc. # All Rights Reserved. # # ####### DESCRIPTION # # This script attempts to detect and repair coherency problems between # the system configuration state and the state saved in the database. # # - Verify and repair virtual sites. # - Verify and repair user accounts. # - Maintain intergrity of the RaQ system configuration files. # - Ensure coherency between System and Meta/postgreSQL backend. # # ####### SUPPORTED SYSTEMS # # Sun Cobalt RaQ 4 product family (en, ja) # # ####### CHANGELOG # # version 3.1 (Jan 07 2002) # - add option to reset symlinks in /home/sites # # version 3.0 (Jun 28 2000) # - updates to support new Meta for RaQ4 # # version 2.1 (Apr 18 2000) # - added 'Y' option to say yes to all # # version 2.0 (Mar 01 2000) # - now has support for rebuilding the virtual site list # - command line options have changed # # version 1.2 (Feb 16 2000) # - don't show conflict on self->self aliases # - vacationmsg field is now blank # # version 1.1 (Feb 04 2000) # - fix display of user info that is longer than page width # - fix alias handling # - verifies system is RaQ3 and user is root # # version 1.0 (Feb 03 2000) # - initial release # - fix incorrect reporting of frontpage state # # version 0.9 (Feb 02 2000) # - verify valid user accounts, prompt database update when changes detected # - prompts for and allows database deletion of sites that do not exist in # the system configuration # - fix int() conversion & mismatched parens # # version 0.8 (Feb 01 2000) # - @{ union,intersect,symmetric difference } for database/system user lists # - detect invalid system users correctly # # version 0.7 (Feb 01 2000) # - meta object containing all user fields, completed with information from # current system configuration and state information # # version 0.1 (Jan 31 2000) # - initial creation # # ####### BEGIN { require Cobalt::Meta; require Cobalt::Meta::vsite; require Cobalt::User; require Cobalt::Vacation; require Cobalt::Email; require Cobalt::List; require Cobalt::Fpx; require Cobalt::Ftp; require Cobalt::Quota; use Getopt::Std; use IO::File; use vars qw($TITLE $VERSION $COPYRIGHT); $TITLE = "meta-verify"; $VERSION = "3.1"; $COPYRIGHT = "Copyright (c) 2002 Sun Microsystems, Inc."; } # verify that this is a supported system if (-e "/etc/build") { if (system("egrep", "-q", "3(100|001|500|599)R", "/etc/build")) { die("\nThis program is designed for the RaQ 4\n\n"); } } else { die("\nThis program is only for the RaQ 4!\n\n"); } # verify that we are root if ($< != 0) { die("\nThis program must be run as root!\n\n"); } # program header printf("\n"); printf("%s (version %s)\n", $TITLE, $VERSION); printf("%s\n", $COPYRIGHT); # global variables use vars qw($Verbose); use vars qw($target $e %eCOUNT @eALL @eGOOD @eBAD); use vars qw(@eFIELDS @eARRAYS); use vars qw(@all_DB @all_SYS $entryX $entry $entryTYPE); use vars qw($login $message $field $DB_field $SYS_field); # format for output of messages format PRINT_MSG = | ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $message ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<... $message . # format for output of messages format PRINT_ENTRY = @>>>>>>>>>>>>>> | ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $login, $message ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<... $message . # format for output when handling entry errors format PRINT_ENTRY_ERROR = @>>>>>>>>>>>>>> | ERROR ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $login, $message ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<... $message . # format for output when processing format PRINT_FIELD = @>>>>>>>>>>>>>> | @>>>>>>>>>>>>> = ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $login, $field, $message ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $message ~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<... $message . # format for error output when comparing meta+system fields format PRINT_FIELD_ERROR = @>>>>>>>>>>>>>> | ERROR IN @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $login, $field | DATABASE ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $DB_field ~ | ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $DB_field ~ | ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<... $DB_field | SYSTEM ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $SYS_field ~ | ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $SYS_field ~ | ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<... $SYS_field . # command-line arguments, verbose level getopts("suvaihdfl"); # usage help &usage if ($opt_h); $Auto = ($opt_f) ? 1 : 0; $Verbose = ($opt_v) ? 1 : 0; if ($opt_u) { # fix users $target = "USERS"; &setup_users(); &diff_arrays(); } elsif ($opt_s) { # fix virtual sites $target = "VIRTUAL SITES"; &setup_vsite(); &diff_arrays(); } elsif ($Auto) { # fix virtual sites $target = "VIRTUAL SITES"; &setup_vsite(); &diff_arrays(); &examine_invalid(@eBAD); # fix users $target = "USERS"; &setup_users(); &diff_arrays(); &examine_invalid(@eBAD); printf("\nOK\n\n"); exit 0; } elsif ($opt_l) { # reset virtual site symlinks with # entries from /etc/httpd/conf/httpd.conf my $root = "/home/sites"; my $httpd_conf = "/etc/httpd/conf/httpd.conf"; my($vpath,$vhost); unless (-s $httpd_conf) { printf("ERROR: $httpd_conf not found\n"); exit 1; } my $htconf = new IO::File "$httpd_conf"; unless (defined $htconf) { printf("ERROR: unable to open $httpd_conf: $!\n"); exit 1; } print "\n"; while (<$htconf>) { if (m{^} ... m{^}) { if (m{^DocumentRoot $root/([^/]+)/web}) { $entry = $1; $vpath = "$root/$entry"; } elsif (m{^ServerName (.+)}) { $host = $1; $vhost = "$root/$host"; } if (m{^}) { next unless (-d $vpath); unlink $vhost if (-e $vhost); print_entry($host); symlink $vpath, $vhost; $vpath=$vhost=""; } } } print "\n"; undef $htconf; exit 0; } else { &usage; } if ($opt_d) { printf("dropping database table...\n"); &Cobalt::Meta::drop($entryTYPE); printf("OK\n\n"); exit 0; } if ($opt_a) { &examine_valid(@eGOOD); } elsif ($opt_i) { &examine_invalid(@eBAD); } else { printf("error - must specify valid (-a) or invalid (-i)\n\n"); exit 1; } print "\n" unless ($Verbose); printf("OK\n\n"); exit 0; 1; sub usage () { print <DESTROY(); next; } # in system but not database if ($inSYS) { my $input; # confirm add while ($input !~ /^[Yyn]/ && !$Auto) { printf(" | - save ALL\n"); printf(" | - save entry to database\n"); printf(" | - do NOT save entry to database\n"); printf(" | - view info obtained from system\n"); printf(" | save? [Yynv] "); $input = ; $Auto++ if ($input =~ /^Y/); # print info if ($input =~ /^v/) { $Verbose++; $entryX->DESTROY; $entryX = &build_system($entry); $Verbose--; } } # always give the chance to bail next if ($input =~ /^n/); # save unless ($entryX->save) { &print_entry_error("unable to save"); $entryX->DESTROY; next; } &print_entry("saved"); $entryX->DESTROY; next; } # in database but not system if ($inDB) { my $input = ''; my $entryY = Cobalt::Meta->new(type => "$entryTYPE"); # confirm database entry delete while ($input !~ /^[yn]/ && !$Auto) { printf(" | - remove database entry\n"); printf(" | - do NOT remove database entry\n"); printf(" | remove? [yn] "); $input = ; } # always give the chance to bail next if ($input =~ /^n/); # delete entry $entryY->retrieve("$entry"); unless ($entryY->delete()) { &print_entry_error("unable to remove"); $entryY->DESTROY(); next; } $entryY->DESTROY(); &print_entry("removed"); next; } # not in database or system &print_entry_error("entry not in database OR system"); next; } return; } # examine_valid () # # check the LIST for valid and verify # coherency of fields between system and database # # correct the problem if possible # # arg: list to check # sub examine_valid () { my (@eLIST) = (@_); return unless (scalar(@eLIST)); printf("verifying %d VALID %s\n\n", scalar(@eLIST), $target); foreach $entry (@eLIST) { # this entry from the database my $entry_DB = (Cobalt::Meta->new(type => "$entryTYPE")); unless ($entry_DB->retrieve("$entry")) { &print_entry_error("entry non-existant in database"); next; } # get info from the system my $entry_SYS = &build_system("$entry"); unless ($entry_SYS) { &print_entry_error("entry does not exist in system"); next; } # compare fields my $errcount = 0; foreach my $fld (@eFIELDS) { if ($entry_DB->{$fld} ne $entry_SYS->{$fld}) { $errcount++; &print_field_error($fld, $entry_DB->{$fld}, $entry_SYS->{$fld}); } } foreach my $fld (@eARRAYS) { my ($e,@aDB,@aSYS,%count,@diff); # turn flat string into list @aDB = split(' ', $entry_DB->{$fld}); @aSYS = split(' ', $entry_SYS->{$fld}); # compute difference of lists foreach $e (@aDB, @aSYS) { $count{$e}++ } foreach $e (keys %count) { push (@diff, $e) unless ($count{$e} == 2) } # if difference then error if (scalar(@diff)) { $errcount++; &print_field_error($fld, join(', ', @aDB), join(', ', @aSYS)); } } if ($errcount) { my $input; # confirm database update while ($input !~ /^[yn]/) { printf(" | - update database info for entry\n"); printf(" | - do NOT update database\n"); printf(" | update? [yn] "); $input = ; } # always give the chance to bail if ($input =~ /^n/) { print "\n"; next; } # save unless ($entry_SYS->save()) { &print_entry_error("unable to save"); print "\n"; } else { &print_entry("updated"); print "\n"; } } else { &print_entry("ok"); print "\n" if ($Verbose); } $entry_DB->DESTROY(); $entry_SYS->DESTROY(); } return; } sub setup_users () { $entryTYPE = "users"; printf("\nchecking $target\n"); @all_SYS = (); @all_DB = (); # some global variables # user fields to compare when verifying all users @eFIELDS = ("name", "fullname", "uid", "vsite", "quota", "vacation", "admin", "shell", "apop", "fpx", "suspend"); # these fields are to be treated as arrays # where order does NOT matter @eARRAYS = ("forward", "aliases"); # the global database views and Meta objects $entryX = Cobalt::Meta->new(type => "users"); @all_DB = $entryX->getall(); @all_SYS = Cobalt::User::user_list(); # remove admin from both sets @all_SYS = grep !/\bdefault\b/, @all_SYS; @all_SYS = grep !/\badmin\b/, @all_SYS; @all_DB = grep !/\bdefault\b/, @all_DB; @all_DB = grep !/\badmin\b/, @all_DB; if ($Verbose) { print "\n"; foreach $e (@all_DB) { &print_field("db user", $e); } foreach $e (@all_SYS) { &print_field("sys user", $e); } print "\n"; } } sub setup_vsite () { printf("\nchecking $target\n"); $entryTYPE = "vsite"; @all_SYS = (); @all_DB = (); # some global variables # user fields to compare when verifying all users @eFIELDS = ("name", "ipaddr", "hostname", "domain", "fqdn", "suspend", "quota", "ftp", "fpx", "ssi", "ssl", "cgi", "casp", ); # these fields are to be treated as arrays # where order does NOT matter @eARRAYS = (); # the global database views and Meta objects $entryX = new Cobalt::Meta::vsite; @all_DB = $entryX->getall(); map { push @all_SYS, @$_[2]; } (Cobalt::Vsite::vsite_list()); # remove from both sets @all_SYS = grep !/\bdefault\b/, @all_SYS; @all_DB = grep !/\bdefault\b/, @all_DB; if ($Verbose) { print "\n"; foreach $e (@all_DB) { &print_field("db vsite", $e); } foreach $e (@all_SYS) { &print_field("sys vsite", $e); } print "\n"; } } sub build_system () { my $ebuild = shift; return unless ($ebuild); if ($entryTYPE eq "users") { return build_user_system($ebuild); } elsif ($entryTYPE eq "vsite") { return build_vsite_system($ebuild); } return; } sub build_vsite_system () { my $site = shift; return unless ($site); my(@tmpARRAY); my($newVSITE) = new Cobalt::Meta::vsite(name => $site); &print_field("group", $newVSITE->name) if ($Verbose); # verify the group existance unless (Cobalt::Group::group_exist("$site")) { &print_entry_error("group does not exist"); return; } # retrieve network info from httpd.conf my($ipaddr,$fqdn) = (Cobalt::Vsite::vsite_get_bygroup("$site"))[0,1]; unless ($ipaddr && $fqdn) { &print_entry_error("does not exist in apache configuration"); return; } $newVSITE->ipaddr($ipaddr); &print_field("ipaddr", $newVSITE->ipaddr) if ($Verbose); $newVSITE->fqdn($fqdn); &print_field("fqdn", $newVSITE->fqdn) if ($Verbose); # parse fqdn for hostname+domain unless ($fqdn =~ /([\w-]+)\.([\w-\.]+)/) { &print_entry_error("unable to split \"$fqdn\" into hostname+domain"); return; } my($hostname,$domain) = ($1,$2); $newVSITE->hostname($1); &print_field("hostname", $newVSITE->hostname) if ($Verbose); $newVSITE->domain($2); &print_field("domain", $newVSITE->domain) if ($Verbose); # get disk quota my($quota) = (Cobalt::Quota::repquota($site, 1))[1]; if ($quota > 0) { $newVSITE->quota(int(($quota * 1024) / 1048576)); } &print_field("disk quota", $newVSITE->quota) if ($Verbose); # get anon ftp settings from proftpd config file my($ftp,$ftpusers,$ftpquota) = Cobalt::Ftp::ftp_get_anonymous($site); if ($ftp) { $newVSITE->ftp(t); $newVSITE->ftpusers($ftpusers); if ($ftpquota > 0) { $newVSITE->ftpquota(int(($ftpquota * 1024) / 1048576)); } } &print_field("anonymous FTP", $newVSITE->get(ftp)) if ($Verbose); # get suspend flag $newVSITE->suspend(Cobalt::Vsite::vsite_issuspend($site)); &print_field("suspended", $newVSITE->get(suspend)) if ($Verbose); # get frontpage settings $newVSITE->fpx(Cobalt::Fpx::fpx_get_web($site)); &print_field("frontpage", $newVSITE->get(fpx)) if ($Verbose); # get chilisoft ASP settings $newVSITE->casp(Cobalt::Vsite::vsite_get_casp($site)); &print_field("Chil!soft ASP", $newVSITE->get(casp)) if ($Verbose); # get cgi settings $newVSITE->cgi(Cobalt::Vsite::vsite_get_cgis($site)); &print_field("CGI scripts", $newVSITE->get(cgi)) if ($Verbose); # get ssi settings $newVSITE->ssi(Cobalt::Vsite::vsite_get_ssi($site)); &print_field("SSI scripts", $newVSITE->get(ssi)) if ($Verbose); # get php settings $newVSITE->php(Cobalt::Vsite::vsite_get_php($site)); &print_field("PHP scripts", $newVSITE->get(php)) if ($Verbose); # get ssl settings $newVSITE->ssl((-e "/etc/httpd/ssl/$site") ? 't' : 'f'); &print_field("SSL server", $newVSITE->get(ssl)) if ($Verbose); printf("\n") if ($Verbose); return $newVSITE; } # build_user_system () # # tries to build a Meta object of type "users" # from information gathered by the system # # arg: login name # ret: Meta object (type => users) # sub build_user_system () { my $user = shift; return unless ($user); my (@tmpARRAY); my $newUSER = Cobalt::Meta->new(type => "users"); # get basic user info my ($name,$uid,$fullname,$dir,$sh) = (getpwnam($user))[0,2,6,7,8]; unless ($name) { &print_entry_error("does not exist in password file"); return; } # determine virtual site from home directory path my $vsite; if ($dir =~ m%^/home/sites/home/users/%) { $vsite = "home"; } elsif ($dir =~ m%^/home/sites/(site[0-9]+)/users/%) { $vsite = "$1"; } else { &print_entry_error("invalid home directory"); return; } printf("\n") if ($Verbose); # user name $newUSER->put(name => "$name"); &print_field("user name", $name) if ($Verbose); # full name $newUSER->put(fullname => "$fullname", altname => ""); &print_field("full name", $fullname) if ($Verbose); # UID $newUSER->put(uid => "$uid"); &print_field("UID", $uid) if ($Verbose); # virtual site membership $newUSER->put(vsite => "$vsite"); &print_field("virtual site", $vsite) if ($Verbose); # disk quota my($quota) = (Cobalt::Quota::repquota($user))[1]; $newUSER->put(quota => (($quota > 0) ? int(($quota * 1024) / 1048576) : 0)); &print_field("disk quota", $newUSER->get(quota)) if ($Verbose); # email aliases my $aliases = ""; @tmpARRAY = (Cobalt::Email::mail_virtuser_get_byuser($user)); if (scalar(@tmpARRAY)) { # remove domains my @oARRAY; foreach (@tmpARRAY) { if (/^([^@]+)\@.*/) { push(@oARRAY, $1) unless($1 eq $user); } else { push(@oARRAY, $_); } } $aliases = join(" ", @oARRAY); } $newUSER->put(aliases => "$aliases"); &print_field("email aliases", $aliases) if ($Verbose); # email forwarding @tmpARRAY = (Cobalt::List::alias_get_vacationless($user)); $newUSER->put(forward => ((scalar(@tmpARRAY) > 0) ? join(" ", @tmpARRAY) : '')); &print_field("email forward", $newUSER->get(forward)) if ($Verbose); # virtual site administrator @tmpARRAY = (Cobalt::User::user_list_groups($user)); $newUSER->put(admin => ((scalar(grep(/\b$vsite\b/, @tmpARRAY))) ? 't' : 'f')); &print_field("site admin", $newUSER->get(admin)) if ($Verbose); # suspended user $newUSER->put(suspend => ((Cobalt::User::user_issuspend($user)) ? 't' : 'f')); &print_field("suspended", $newUSER->get(suspend)) if ($Verbose); # telnet/shell access $newUSER->put(shell => (($sh eq "/bin/bash") ? 't' : 'f')); &print_field("telnet/shell", $newUSER->get(shell)) if ($Verbose); # authenticated pop3 (APOP) $newUSER->put(apop => ((Cobalt::Email::mail_apop_isuser($user)) ? 't' : 'f')); &print_field("secure POP3", $newUSER->get(apop)) if ($Verbose); # frontpage extensions @tmpARRAY = (Cobalt::Vsite::vsite_get_fpx($vsite)); $newUSER->put(fpx => ((scalar(grep(/\b$user\b/, @tmpARRAY))) ? 't' : 'f')); &print_field("frontpage", $newUSER->get(fpx)) if ($Verbose); # email vacation responder $newUSER->put(vacation => ((Cobalt::Vacation::vacation_get_on($user)) ? 't' : 'f')); &print_field("vacation", $newUSER->get(vacation)) if ($Verbose); printf("\n") if ($Verbose); return $newUSER; }