initial commit of file from CVS for e-smith-lib on Wed 12 Jul 08:58:46 BST 2023

This commit is contained in:
Brian Read
2023-07-12 08:58:46 +01:00
parent 6d7e97ea37
commit a527984040
98 changed files with 14369 additions and 2 deletions

View File

@@ -0,0 +1,540 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::AccountsDB;
use strict;
use warnings;
use esmith::db;
use vars qw( $AUTOLOAD @ISA );
use esmith::DB::db;
@ISA = qw(esmith::DB::db);
=head1 NAME
esmith::AccountsDB - interface to esmith configuration database
=head1 SYNOPSIS
use esmith::AccountsDB;
my $a = esmith::AccountsDB->open;
my @users = $a->users();
my @groups = $a->groups();
my @ibays = $a->ibays();
my @printers = $a->printers();
my @pseudonyms = $a->pseudonyms();
$a->is_user_in_group($user, $group);
my @groups = $a->user_group_list($user);
$a->add_user_to_groups($user, @groups);
$a->remove_user_from_groups($user, @groups);
$a->create_user_auto_pseudonyms($user);
$a->remove_user_auto_pseudonyms($user);
$a->remove_all_user_pseudonyms($user);
my $dp = $a->dot_pseudonym($user);
my $up = $a->underbar_pseudonym($user);
my $uid = $a->get_next_uid();
=head1 DESCRIPTION
This module provides an abstracted interface to the esmith accounts
database.
=cut
our $VERSION = sprintf '%d.%03d', q$Revision: 1.18 $ =~ /: (\d+).(\d+)/;
=head2 open()
Loads an existing account database and returns an esmith::AccountsDB
object representing it.
=begin testing
use esmith::TestUtils qw(scratch_copy);
use_ok("esmith::AccountsDB");
use esmith::db;
use vars qw($a);
my $conf = scratch_copy('10e-smith-lib/accounts.conf');
$a = esmith::AccountsDB->open($conf);
isa_ok($a, 'esmith::AccountsDB');
is( $a->get("global")->prop('type'), "system", "We can get stuff from the db");
=end testing
=cut
sub open {
my($class, $file) = @_;
$file = $file || $ENV{ESMITH_ACCOUNT_DB} || "accounts";
return $class->SUPER::open($file);
}
=head2 open_ro()
Like esmith::DB->open_ro, but if given no $file it will try to open the
file in the ESMITH_ACCOUNT_DB environment variable or accounts.
=begin testing
=end testing
=cut
sub open_ro {
my($class, $file) = @_;
$file = $file || $ENV{ESMITH_ACCOUNT_DB} || "accounts";
return $class->SUPER::open_ro($file);
}
=head2 users(), groups(), ibays(), printers(), pseudonyms()
Returns a list of records (esmith::DB::db::Record objects) of the
given type.
=for testing
foreach my $t (qw(users groups pseudonyms)) {
my @list = $a->$t();
ok(@list, "Got a list of $t");
isa_ok($list[0], 'esmith::DB::db::Record');
}
=cut
sub AUTOLOAD {
my $self = shift;
my ($called_sub_name) = ($AUTOLOAD =~ m/([^:]*)$/);
my @types = qw( users groups ibays printers pseudonyms);
if (grep /^$called_sub_name$/, @types) {
$called_sub_name =~ s/s$//g; # de-pluralize
return $self->get_all_by_prop(type => $called_sub_name);
}
}
=head1 GROUP MANAGEMENT
=head2 $a->is_user_in_group($user, $group)
Returns true if the user is a member of the group, false otherwise. The
arguments are a user name and a group name.
This routine will return undef if there is no such group, false (but
defined) if the user is not in the group, and true if the user is in the
group.
=for testing
ok($a->is_user_in_group('bart', 'simpsons'), "Bart is in group Simpsons");
ok(not($a->is_user_in_group('moe', 'simpsons')), "Moe is not in group Simpsons");
ok(not(defined $a->is_user_in_group('moe', 'flanders')), "No such group as Flanders");
=cut
sub is_user_in_group {
my ($self, $user, $group) = @_;
$group = $self->get($group) || return undef;
my $members = $group->prop('Members');
return grep(/^$user$/, split /,/, $members) ? 1 : 0;
}
=head2 $a->user_group_list($user)
Get a list of groups (by name) of which a user is a member. The $user argument
is simply the username.
=for testing
my @groups = $a->user_group_list('bart');
is_deeply(\@groups, ['simpsons'], "Bart's group list is 'simpsons'");
=cut
sub user_group_list {
my ($self, $user) = @_;
my @groups = $self->groups();
my @user_groups;
foreach my $g (@groups) {
push(@user_groups, $g->key())
if $self->is_user_in_group($user, $g->key());
}
return @user_groups;
}
=head2 $a->add_user_to_groups($user, @groups)
Given a list of groups (by name), adds the user to all of them.
Doesn't signal the group-modify event, just does the DB work.
Note: the method used here is a bit kludgy. It could result in a user
being in the same group twice.
=for testing
my @groups = $a->groups();
$a->remove_user_from_groups('maggie', map { $_->key() } @groups);
my @mg = $a->user_group_list('maggie');
is(scalar @mg, 0, "Maggie has been removed from all groups");
$a->add_user_to_groups('maggie', 'simpsons');
@mg = $a->user_group_list('maggie');
is_deeply(\@mg, ['simpsons'], "Maggie has been added to group 'simpsons'");
$a->remove_user_from_groups('maggie', 'simpsons');
@mg = $a->user_group_list('maggie');
is_deeply(\@mg, [], "Maggie's been removed from all groups again");
$a->set_user_groups('maggie', 'simpsons');
@mg = $a->user_group_list('maggie');
is_deeply(\@mg, ['simpsons'], "Maggie's groups have been set to: 'simpsons'");
=cut
sub add_user_to_groups {
my ($self, $user, @groups) = @_;
GROUP: foreach my $group (@groups) {
unless (($group) = ($group =~ /(^[\w.-]+$)/))
{
warn "Group name doesn't look like a group!\n";
next GROUP;
}
my $group_rec = $self->get($group) || next GROUP;
my @members = split(/,/, $group_rec->prop('Members'));
push @members, $user;
# Remove duplicates
my %members = map { $_ => 1 } @members;
$group_rec->set_prop('Members', join(',', sort keys %members));
}
}
=head2 $a->remove_user_from_groups($user, @groups)
Given a list of groups, removes a user from all of them.
Doesn't signal the group-modify event, just does the DB work.
=cut
sub remove_user_from_groups {
my ($self, $user, @groups) = @_;
GROUP: foreach my $g (@groups) {
my $group_rec = $self->get($g) || next GROUP;
my $members = $group_rec->prop('Members');
my @members = split (/,/, $members);
@members = grep (!/^$user$/, @members);
@members = qw(admin) unless @members; # admin *must* be in every group
$group_rec->set_prop('Members', join(',', @members));
}
}
=head2 $a->set_user_groups($user, @groups)
Sets the user's groups in one fell swoop. Under the hood, it's removing
the user from every group they're in then adding them to the set you give.
=cut
sub set_user_groups
{
my ($self, $user, @groups) = @_;
my @old_groups = $self->user_groups_list($user);
$self->remove_user_from_groups($user, @old_groups);
$self->add_user_to_groups($user, @groups);
}
=head1 PSEUDONYM MANAGEMENT
=head2 $a->create_user_auto_pseudonyms($user)
Given a user name, creates standard pseudonyms ("dot" and "underbar" style)
for that user.
=for testing
my $user = 'bart';
ok($a->pseudonyms(), "There are pseudonyms in the accounts db");
$a->remove_user_auto_pseudonyms($user);
ok(! $a->get('bart.simpson'), "Removed dot-pseudonym");
ok(! $a->get('bart_simpson'), "Removed underbar-pseudonym");
$a->create_user_auto_pseudonyms($user);
ok($a->get('bart.simpson'), "Created dot-pseudonym");
ok($a->get('bart_simpson'), "Created underbar-pseudonym");
=cut
sub create_user_auto_pseudonyms {
my ($self, $user) = @_;
my $user_rec = $self->get($user);
my $firstName = $user_rec->prop("FirstName");
my $lastName = $user_rec->prop("LastName");
my $dot_pseudonym = dot_pseudonym($self, $user);
my $underbar_pseudonym = underbar_pseudonym($self, $user);
my $dot_acct = $self->get($dot_pseudonym) ||
$self->new_record($dot_pseudonym, { type => 'pseudonym',
Account => $user} );
my $underbar_acct = $self->get($underbar_pseudonym) ||
$self->new_record($underbar_pseudonym, { type => 'pseudonym',
Account => $user} );
}
=head2 $a->remove_all_user_pseudonyms($user)
Given a username, remove any pseudonyms related to that user from the
accounts database. Also removes any pseudonyms related to a pseudonym
being removed. Returns the number of pseudonym records deleted.
=cut
sub remove_all_user_pseudonyms {
my ($self, $user) = @_;
my $count = 0;
foreach my $p_rec (grep { $_->prop("Account") eq $user } $self->pseudonyms())
{
foreach my $p_p_rec (grep { $_->prop("Account") eq $p_rec->key } $self->pseudonyms())
{
$p_p_rec->delete;
$count++;
}
$p_rec->delete;
$count++;
}
return $count;
}
=head2 $a->remove_user_auto_pseudonyms($user)
Given a username, remove the dot_pseudonym and underbar_pseudonym
related to that user from the accounts database. Returns the number
of pseudonym records deleted.
=cut
sub remove_user_auto_pseudonyms {
my ($self, $user) = @_;
my $dot_pseudonym = dot_pseudonym($self, $user);
my $underbar_pseudonym = underbar_pseudonym($self, $user);
my $count = 0;
foreach my $p_rec ($self->get($dot_pseudonym),
$self->get($underbar_pseudonym))
{
if (defined $p_rec && $p_rec->prop("type") eq "pseudonym" &&
$p_rec->prop("Account") eq $user)
{
$p_rec->delete;
$count++;
}
}
return $count;
}
=head2 $a->dot_pseudonym($user)
Returns the "dot"-style pseudonym for a user as a string. For instance,
dot_pseudonym("bart") might return "bart.simpson".
=cut
sub dot_pseudonym {
my ($self, $user) = @_;
my $user_rec = $self->get($user);
my $firstName = $user_rec->prop("FirstName");
my $lastName = $user_rec->prop("LastName");
my $dot_pseudonym = lc("$firstName $lastName");
$dot_pseudonym =~ s/^\s+//; # Strip leading whitespace
$dot_pseudonym =~ s/\s+$//; # Strip trailing whitespace
$dot_pseudonym =~ s/\s+/ /g; # Multiple spaces become single spaces
$dot_pseudonym =~ s/\s/./g; # Change all spaces to dots
return $dot_pseudonym;
}
=head2 $a->underbar_pseudonym($user)
Returns the "underbar"-style pseudonym for a user as a string. For instance,
underbar_pseudonym("bart") might return "bart_simpson".
=begin testing
my @users = $a->users();
my $user = 'bart';
my $rec = $a->get($user);
my $firstName = $rec->prop("FirstName");
my $lastName = $rec->prop("LastName");
my $up = $a->underbar_pseudonym($user);
is($up, "bart_simpson", "Underbar pseudonym created correctly");
my $dp = $a->dot_pseudonym($user);
is($dp, "bart.simpson", "Underbar pseudonym created correctly");
=end testing
=cut
sub underbar_pseudonym {
my ($self, $user) = @_;
my $user_rec = $self->get($user);
my $firstName = $user_rec->prop("FirstName");
my $lastName = $user_rec->prop("LastName");
my $underbar_pseudonym = lc("$firstName $lastName");
$underbar_pseudonym =~ s/^\s+//; # Strip leading whitespace
$underbar_pseudonym =~ s/\s+$//; # Strip trailing whitespace
$underbar_pseudonym =~ s/\s+/ /g; # Multiple spaces become single spaces
$underbar_pseudonym =~ s/\s/_/g; # Change all spaces to underbars
return $underbar_pseudonym;
}
=head2 $a->activeUsers()
Returns the number of active users, ie, accounts which have passwords set and
are of type 'user'.
=begin testing
my $numActiveUsers = scalar $a->activeUsers();
like($numActiveUsers, qr/[0-9]+/, "active users returns a number");
=end testing
=cut
sub activeUsers()
{
my $self = shift;
my @users = $self->users();
return unless @users;
return grep { $_->prop("PasswordSet") eq 'yes' } @users;
}
=head2 get_next_uid
Returns the next available UID from /etc/passwd. All UIDs are above the range
reserved for 'system' accounts (currently 5000).
=for testing
SKIP: {
skip "Must be root to run get_next_uid" if $<;
my $u = $a->get_next_uid();
ok($u > 5000, "UID should be greater than 5000");
ok(! getpwuid($u), "UID should not yet exist");
}
=cut
sub get_next_uid {
use esmith::ConfigDB;
my $id;
my $db = esmith::ConfigDB->open || die "Couldn't open config db";
if ($id = $db->get('MinUid'))
{
$id = $id->value();
}
else
{
$db->new_record('MinUid');
$id = 5000;
}
my $maxid = 1 << 31;
setpwent();
setgrent();
while (getpwuid $id || getgrgid $id)
{
die "All userids in use" if ($id == $maxid);
$id++;
}
endpwent();
endgrent();
$db->set_value('MinUid', $id + 1);
return $id;
}
=pod
=head2 new_record ($key, \%props)
This method is overridden from esmith::DB::db. We do an additional check
for implicit accounts here - accounts that exist in /etc/passwd but not
in the db. Otherwise it behaves just like the superclass method.
=begin testing
isnt($a->new_record("root", {type=>'system'}), "OK",
"can't create existing account");
is($a->get("nobody"), undef, "nobody doesn't exist in db");
isnt($a->new_record("nobody", {type=>'system'}), "OK",
"can't create account in /etc/passwd");
isnt($a->new_record("screwy", {type=>'user'}), undef,
"created a regular user");
=end testing
=cut
sub new_record
{
my ($self, $key, $props) = @_;
if(getpwnam($key) || getgrnam($key))
{
warn "Attempt to create account '$key' which already exists ",
"in passwd";
return undef;
}
return $self->SUPER::new_record($key, $props);
}
=pod
=head2 validate_account_name ($name)
Check $name to see if it is a valid account name. Valid account names
start with a letter or number and contain only letters, numbers,
underscores, dots and dashes.
=begin testing
is($a->validate_account_name("root"), "OK", "root is a valid name");
is($a->validate_account_name("fred.frog"), "OK", "fred.frog is a valid name");
is($a->validate_account_name("jane_doe"), "OK", "jane_doe is a valid name");
isnt($a->validate_account_name("^root"), "OK", "^root is not a valid name");
is(esmith::AccountsDB::validate_account_name("root"), "OK", "called as a function");
=end testing
=cut
sub validate_account_name
{
my $self;
# Were we called as a method or a function?
if($#_ > 0)
{
$self = shift;
}
my $name = shift;
return ($name =~ /[^0-9a-z\-_\.]/ or $name !~ /^[a-z]/) ? undef : 'OK';
}
=head1 AUTHOR
SME Server Developers <bugs@e-smith.com>
See http://www.e-smith.org/ for more information

View File

@@ -0,0 +1,398 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::ConfigDB;
use strict;
use warnings;
use vars qw( $AUTOLOAD @ISA );
use esmith::DB::db;
@ISA = qw( esmith::DB::db );
use esmith::ConfigDB::Record;
=head1 NAME
esmith::ConfigDB - interface to esmith configuration database
=head1 SYNOPSIS
use esmith::ConfigDB;
my $db = esmith::ConfigDB->open;
my $db = esmith::ConfigDB->open_ro;
my @services = $db->services();
# Singleton Records
my $record = $db->get($key);
my $value = $record->value;
$record->set_value($value);
# BAD!
my $value = $db->get($key)->value() # Throws a runtime error if $key
# doesn't exist
$value = $db->get($key)->prop($p) # Throws a runtime error if $key
# doesn't exist
# GOOD
my $record = $db->get($key);
my $value;
if ($record)
{
$value = $record->prop($prop);
}
# Typed Records (eventually they all will be)
my $prop = $record->prop($p);
$record->set_prop($prop, $propvalue);
my $value = $db->get_value($key) # Returns undef if record doesn't exist
$value = $db->get_prop($key, $p) # Returns undef if record doesn't exist
=head1 DESCRIPTION
This module provides an abstracted interface to the esmith master
configuration database.
Unless otherwise noted, esmith::ConfigDB acts like esmith::DB::db.
=cut
our $VERSION = sprintf '%d.%03d', q$Revision: 1.29 $ =~ /: (\d+).(\d+)/;
=head2 open()
Like esmith::DB->open, but if given no $file it will try to open the
file in the ESMITH_CONFIG_DB environment variable or configuration.
=begin testing
use esmith::TestUtils qw(scratch_copy);
use_ok("esmith::ConfigDB");
my $scratch_copy_of_conf = scratch_copy('10e-smith-lib/configuration.conf');
$C = esmith::ConfigDB->open($scratch_copy_of_conf);
isa_ok($C, 'esmith::ConfigDB');
is( $C->get("AccessType")->prop('type'), "dedicated",
"We can get stuff from the db");
is( $C->get_prop("AccessType",'type'), "dedicated",
"We can get_prop stuff from the db");
is( $C->get_value("AccessType"), "dedicated",
"We can get_value stuff from the db");
is( $C->get_value("NoSuchKey"), undef,
"We can get_value non-existant keys");
is( $C->get_prop("diald","status"), "disabled",
"We can get_prop stuff from the db");
is( $C->get_prop("NoSuchKey","NoSuchProp"), undef,
"We can get_prop non-existant keys");
is( $C->get_prop("diald","NoSuchProp"), undef,
"We can get_prop non-existant props");
=end testing
=cut
sub open
{
my ( $class, $file ) = @_;
$file = $file || $ENV{ESMITH_CONFIG_DB} || "configuration";
return $class->SUPER::open($file);
}
=head2 open_ro()
Like esmith::DB->open_ro, but if given no $file it will try to open the
file in the ESMITH_CONFIG_DB environment variable or configuration.
=begin testing
=end testing
=cut
sub open_ro
{
my ( $class, $file ) = @_;
$file = $file || $ENV{ESMITH_CONFIG_DB} || "configuration";
return $class->SUPER::open_ro($file);
}
=head2 new_record()
This method creates a new record in the configuration database. As arguments,
it expects the key to the record, followed by a hash references with its
properties, including the type.
my $db = esmith::ConfigDB->open;
my $record = $db->new_record('zope', { type => 'service',
status => 'disabled' });
my %defaults = qw(
type => 'service',
status => 'disabled',
maintainer => 'admin@domain.com'
);
my $record = $db->get('zope');
unless ($record)
{
$record = $db->new_record('zope', \%defaults);
}
=head2 get()
Like their esmith::DB counterparts except they return
esmith::ConfigDB::Record objects which have a few extra methods.
my $record = $db->get('zope');
=begin testing
my $rec = eval { $C->get("I_dont_exist"); };
ok( !$rec, 'get() on a non-existent key' );
is( $@, '', ' doesnt blow up' );
isa_ok( $C->get("AccessType"), 'esmith::ConfigDB::Record',
"get()ened records are ConfigDB::Records" );
$rec = $C->new_record("I_dont_exist", { type => "foo" });
isa_ok( $rec, 'esmith::ConfigDB::Record',
"new_record()s are ConfigDB::Records" );
$rec->delete("I_dont_exist");
ok( !$C->get("I_dont_exist"), 'delete()' );
=end testing
=cut
sub get
{
my ($self) = shift;
my $rec = $self->SUPER::get(@_);
return $rec ? bless $rec, 'esmith::ConfigDB::Record' : undef;
}
sub new_record
{
my ($self) = shift;
my $rec = $self->SUPER::new_record(@_);
return $rec ? bless $rec, 'esmith::ConfigDB::Record' : undef;
}
=pod
=head2 getLocale()
Retrieves the locale and keyboard settings from the configuration database.
Returns ($lang, $kbdtype, $keytable) on success. Returns undef if the record
doesn't exist.
=cut
sub getLocale
{
my $self = shift;
my $rec = $self->get('sysconfig') or return undef;
my $lang = $rec->prop('Language') || 'en_US';
my $kbdtype = $rec->prop('KeyboardType') || 'pc';
my $keytable = $rec->prop('Keytable') || 'us';
return ( $lang, $kbdtype, $keytable );
}
=pod
=head2 hosts_allow_spec ($service [,$daemon])
Given a service, return the string suitable for /etc/hosts.allow,
checking to see if the service is defined, whether it is enabled and
whether access is set to public, private, or localhost.
An optional argument provides the tag which appears in hosts.allow. If not
given, the service name is used.
For example, one of the following:
# 'oidentd' is not defined in the configuration database
# 'oidentd' is disabled in the configuration database
in.identd: 127.0.0.1
in.identd: 127.0.0.1 192.168.1.1/255.255.255.0
in.identd: ALL
And here's the hosts.allow fragment:
{
$OUT = $DB->hosts_allow_spec('oidentd', 'in.identd');
}
=cut
sub hosts_allow_spec
{
my $self = shift;
my $service_name = shift;
my $daemon = shift || $service_name;
my $service = $self->get($service_name)
or return
"# '$service_name' is not defined in the configuration database";
my $status = $service->prop('status') || "disabled";
return "# '$service_name' is disabled in the configuration database"
unless ( $status eq "enabled" );
my $access = $service->prop('access') || "private";
use esmith::NetworksDB;
my $ndb = esmith::NetworksDB->open_ro;
my @spec = ( "$daemon:", $ndb->local_access_spec($access) );
return "@spec";
}
=pod
=head2 wins_server
Return the value of the WINS server from the config db
or undef if we don't have a WINS server set and we are
not the domain master
=cut
sub wins_server
{
my ($self) = @_;
my $wins_server = $self->get_prop( 'smb', 'WINSServer' );
return $wins_server if $wins_server;
my $server_role = $self->get_prop( 'smb', 'ServerRole' ) || 'WS';
return $self->get_prop( 'InternalInterface', 'IPAddress' )
if $server_role =~ m{^(PDC|ADS)$};
return undef;
}
=pod
=head2 services()
Returns a list of services in the configuration database
=for testing
foreach my $t (qw(services)) {
my @list = $C->$t();
ok(@list, "Got a list of $t");
}
=cut
sub AUTOLOAD
{
my $self = shift;
my ($called_sub_name) = ( $AUTOLOAD =~ m/([^:]*)$/ );
my @types = qw( services );
if ( grep /^$called_sub_name$/, @types )
{
$called_sub_name =~ s/s$//g; # de-pluralize
return $self->list_by_type($called_sub_name);
}
}
=pod
=head2 _loadDefaults ($forceReset)
Behaves just like the esmith::DB method of the same name. This is a private
method used internally.
=begin testing
$scratch_copy_of_conf = scratch_copy('10e-smith-lib/configuration.conf', 'configuration.conf.scratch');
is ($scratch_copy_of_conf, 'configuration.conf.scratch', 'scratch copy name');
$ENV{'ESMITH_CONFIG_DB'} = $scratch_copy_of_conf;
$C = esmith::ConfigDB->open();
is ($C->{file}, $scratch_copy_of_conf, 'file name');
my $accesstype = $C->get("AccessType")->value;
ok($C->new_record('foobar', {type=>'service', status=>'disabled'}),
"Set up foobar record");
is($C->get('foobar')->prop('status'), "disabled", "foobar is disabled");
$ENV{'ESMITH_DB_DEFAULTSDIR'} = "10e-smith-lib/db";
ok($C->_loadDefaults(), "Loaded defaults");
is($C->get('foobar')->prop('status'), 'enabled', "We forced status enabled");
is($C->get('bazbar')->prop('status'), 'enabled', "We included a new default");
is($C->get('AccessType')->value, $accesstype, "AccessType has not changed");
=end testing
=head2 record_has_defaults ($name)
Behaves just like the esmith::DB method of the same name.
=begin testing
$ENV{'ESMITH_DB_DEFAULTSDIR'} = "10e-smith-lib/db";
is($C->record_has_defaults('foobar'), 1, "foobar has some defaults");
is($C->record_has_defaults('notthisone'), undef, "notthisone does not");
=end testing
=cut
# There would normally be a method here, but we inherit _loadDefaults
# from esmith::DB. The tests need to go here because the superclass is
# all virtual and testing requires concrete open/get/set methods.
=pod
=head2 migrate
Just like the esmith::DB method of the same name.
=begin testing
$scratch_copy_of_conf = scratch_copy('10e-smith-lib/configuration.conf', 'configuration.conf.scratch');
is ($scratch_copy_of_conf, 'configuration.conf.scratch', 'scratch copy name');
$ENV{'ESMITH_CONFIG_DB'} = $scratch_copy_of_conf;
$C = esmith::ConfigDB->open();
is($C->get('quux'), undef, "No quux here");
$ENV{'ESMITH_DB_DEFAULTSDIR'} = "10e-smith-lib/db";
ok($C->migrate(), "We can migrate");
my $quux = $C->get('quux');
ok($quux, "We got quux");
is($quux->prop('status'), 'enabled', "We migrated to quux");
$quux->delete;
=end testing
=cut
# There would normally be a method here, but we inherit migrate
# from esmith::DB. The tests need to go here because the superclass is
# all virtual and testing requires concrete open/get/set methods.
=head1 AUTHOR
SME Server Developers <bugs@e-smith.com>
=head1 SEE ALSO
L<esmith::DB>, L<esmith::DB::db>, L<esmith::AccountsDB>, L<esmith::DomainsDB>,
L<esmith::HostsDB>, L<esmith::NetworksDB>, L<esmith::ConfigDB::Record>
=cut
1;

View File

@@ -0,0 +1,108 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::ConfigDB::Record;
use strict;
use warnings;
use esmith::ConfigDB;
require esmith::DB::db::Record;
our @ISA = qw(esmith::DB::db::Record);
=head1 NAME
esmith::ConfigDB::Record - record in an esmith::ConfigDB database.
=head1 SYNOPSIS
Just like esmith::DB::db::Record except...
my $value = $record->value;
$record->set_value($value);
=head1 DESCRIPTION
This provides some extra functionality needed by the esmith::ConfigDB
databases.
Unless noted, it works just like esmith::DB::db::Record.
=head2 New Methods
=over 4
=item B<value>
=item B<set_value>
my $value = $record->value;
$record->set_value($value);
Gets/sets the value of the $record. Some ConfigDB entries don't have
a set of properties, but rather a single value.
It will warn if you use these on $records with properties.
=begin testing
use esmith::ConfigDB;
$Scratch_Conf = '10e-smith-lib/scratch.conf';
unlink $Scratch_Conf;
$c = esmith::ConfigDB->create($Scratch_Conf);
END { unlink $Scratch_Conf }
{
my $warning = '';
local $SIG{__WARN__} = sub { $warning = join '', @_ };
my $wib = $c->new_record('wibble', { type => 'yar' });
$wib->set_value('foo');
is( $wib->value, 'foo', 'value/set_value' );
is( $warning, '', ' no warning' );
$wib->set_prop(bar => 42);
is( $wib->value, 'foo' );
like( $warning, qr/value\(\) should not be used on records with properties, use prop\(\)/, 'value() warns if the record has props');
$wib->set_value(92);
like( $warning, qr/set_value\(\) should not be used on records with properties, use set_prop\(\)/, 'value() warns if the record has props');
is( $wib->value, 92 );
}
=end testing
=cut
sub value {
my($self) = shift;
my %props = $self->props;
warn "value() should not be used on records with properties, use prop()" if
keys %props > 1;
return $self->prop('type');
}
sub set_value {
my($self, $value) = @_;
my %props = $self->props;
warn "set_value() should not be used on records with properties, ".
"use set_prop()" if keys %props > 1;
return $self->set_prop('type', $value);
}
=back
=head1 SEE ALSO
L<esmith::ConfigDB>, L<esmith::DB::db::Record>
=cut
1;

View File

@@ -0,0 +1,22 @@
#----------------------------------------------------------------------
# Copyright 1999-2008 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::ConfigDB::UTF8;
use strict;
use warnings;
use esmith::DB::db;
use esmith::config::utf8;
our @ISA = qw( esmith::DB::db );
sub tie_class
{
return 'esmith::config::utf8';
}
1;

View File

@@ -0,0 +1,705 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::DB;
use strict;
use warnings;
use Carp;
use File::Basename;
use esmith::templates;
use constant TRUE => 1;
use constant FALSE => 0;
our $VERSION = sprintf '%d.%03d', q$Revision: 1.40 $ =~ /: (\d+).(\d+)/;
our $Error = undef;
=head1 NAME
esmith::DB - virtual interface to E-Smith databases
=head1 SYNOPSIS
# Note: Do not instantiate this class directly. Use a subclass.
# Examples in this documentation where you see it being used directly
# are merely for consistency. Substitute a subclass in our examples.
use esmith::DB;
my $db = esmith::DB->create($filename) or
die esmith::DB->error;
my $db = esmith::DB->open($filename) or
die esmith::DB->error;
my $db = esmith::DB->open_ro($filename) or
die esmith::DB->error;
my %DB = esmith::DB->as_hash($filename) or
die esmith::DB->error;
$db->reload;
my $file = $db->file;
my $record = $db->new_record($key, \%properties);
my $record = $db->get($key);
my @all_records = $db->get_all;
my @all_records_by_prop = $db->get_all_by_prop($prop => $val);
$db->set_prop($key, $prop, $value);
$db->set_value($key, $value);
$db->set_prop($key, $prop, $value, type => $type);
$db->set_value($key, $value, create => 0);
=head1 DESCRIPTION
This module is a general interface to E-Smith's databases of various
types and formats. It is not intended to be used directly, but that
subclasses will implement the interface presented here to provide a
single interface no matter what the underlying format.
For example, there is esmith::DB::db to interface with esmith::db
flatfile databases. There could also be esmith::DB::Berkeley to use
Berkeley database files, or even esmith::DB::DBI.
Most of the methods herein are "virtual". They don't exist. The
subclass is responsible for impelmenting them. There are a handful of
concrete methods that have been implemented for you that should work
with any subclass.
=head2 Virtual Methods
This is the esmith::DB interface. Subclassers are expected to
implement these methods.
=over 4
=item I<create>
my $db = esmith::DB->create($new_config_file) ||
die esmith::DB->error;
Creates a brand new, empty configuration database and returns a
subclass of the esmith::DB object representing it.
Should the $new_config_file already exist or for some reason you can't
write to it, esmith::DB->error will return the reason and
create() will return false.
=item I<open>
my $db = esmith::DB->open($config_file) ||
die esmith::DB->error
Loads an existing configuration database and returns a
subclass of the esmith::DB::db object representing it.
Should the $config_file not exist or not be openable it will return
false and esmith::DB->error will contain the reason.
=item I<open_ro>
my $db = esmith::DB->open_ro($config_file) ||
die esmith::DB->error;
Like open(), but the database is read-only. new_record() and all methods
which could change a record (set_prop(), merge_props(), delete(), etc...)
will both throw exceptions if used.
=item I<error>
my $error = esmith::DB->error;
Returns a string describing the error from the last failing method.
=item I<reload>
$db->reload;
Flushes out the $db's cache (if there is one) and reloads all
configuration data from disk.
=item I<file>
my $file = $db->file;
File which this $db represents.
=item I<new_record>
my $record = $db->new_record($key, \%properties);
Adds a new record at $key in the $db setting it to the given
%properties. Returns a subclass of the esmith::DB::Record object.
If a record already exists for the $key it will return false.
=item I<get>
my $record = $db->get($key);
Gets an existing record from the $db with the given $key. Returns an
esmith::DB::Record object representing the data in $key.
If there's no record for the $key it will return false.
=item I<get_all>
my @records = $db->get_all;
Gets all the records out of the given $db as a list of
esmith::DB::Record objects.
=back
=head2 Concrete methods
These are all implemented in terms of the esmith::DB interface and
its not necessary for a subclass to implement them.
=over 4
=item I<as_hash>
my %db = $db->as_hash;
my %db = esmith::DB->as_hash($file);
Returns the entire database as a hash of hashes. Each key is a key in
the database, and the value is a hash of it's properties.
my $value = $db{some_key}{some_prop};
When used as an object method it will use the already opened database.
When used as a class method it will open the given $file.
=cut
sub as_hash
{
my ( $proto, $file ) = @_;
my $class = ref $proto || $proto;
my $self;
if ( ref $proto )
{ # object method
$self = $proto;
}
else
{ # class method
$self = $class->open($file) or return;
}
my %hash = ();
foreach my $rec ( $self->get_all )
{
my $key = $rec->key;
my %props = $rec->props;
# Setup the hash
$hash{$key} = \%props;
}
return %hash;
}
=item I<get_all_by_prop>
my @records_by_prop = $db->get_all_by_prop($property => $value);
Like get_all() except it gets only those records whose $property has
the given $value. For properties with multiple comma-delimited values
(ie: name|val1,val2,val3), only one of the properties needs to match.
=cut
sub get_all_by_prop
{
my ( $self, $prop, @rest ) = @_;
my %props;
if ( ref($prop) eq 'HASH' )
{
carp "get_all_by_prop called with anonymous hash argument";
%props = ( %{$prop} );
}
else
{
%props = ($prop, @rest);
}
my @things = sort { $a->key cmp $b->key } grep
{
my $found = 1;
while ( my ($p, $v) = each (%props) )
{
$found &= ( defined $_->prop($p) and $_->prop($p) =~ /(?:^|,)$v(?:,|$)/ );
}
$found;
} $self->get_all;
@things;
}
=item I<get_value>
my $value = $db->get_value( $key );
Shortcut method to get the value from the record defined by the given
key. Returns undef if the record does not exist.
The following code is unsafe if the key doesn't exist:
my $value = $db->get("foo")->value || 'default';
and should be:
my $value = 'default';
if (my $r = $db->get("foo"))
{
$value = $r->value;
}
With this method, you can use:
my $value = $db->get_value("foo") || 'default';
=cut
sub get_value
{
my $self = shift;
my $item = $self->get(shift);
return undef unless $item;
return $item->value;
}
=item I<get_prop>
my $prop = $db->get_prop( $key, $prop );
Shortcut method to get a property from the record defined by the given key.
Returns undef if the record for that key doesn't exist, or the property does
not exist.
The following code is unsafe if either the key or property doesn't exist:
my $status = $db->get("foo")->prop('status') || 'disabled';
and should be written as:
my $status;
if (my $s = $db->get("foo"))
{
$status = $s->prop('status');
}
$status ||= "default";
With this method, you can use:
my $value = $db->get_prop("foo", "status") || 'default';
=cut
sub get_prop
{
my $self = shift;
my $item = $self->get(shift);
return undef unless $item;
return $item->prop(shift);
}
=item I<set_value>
$db->set_value($key, $value)[, create => 1]);
Shortcut method to set a value to a key in the database without extracting the
record first.
If the record is not pre-existing, it will be created, unless the 'create'
option is passed with a value of 0.
Returns 0 for any errors, 1 for success.
=cut
sub set_value
{
my $self = shift;
my ($key, $value, %options) = @_;
my %defaults = (create => 1);
%options = (%defaults, %options);
my $record = $self->get($key);
unless ($record)
{
if ($options{create})
{
$record = $self->new_record($key, {type => $value})
or return 0;
}
else
{
return 0;
}
}
$record->set_value($value)
or return 0;
return 1;
}
=item I<set_prop>
$db->set_prop($key, $prop, $value[, type => $type]);
Shortcut method to set a property on a record without having to extract the
record first.
If the optional type option is passed, it will be used to create the record if
it does not already exist. Otherwise, a non-existent record will cause this
method to return an error.
Returns 0 for any errors, 1 for success.
=cut
sub set_prop
{
my $self = shift;
my ($key, $prop, $value, %options) = @_;
my %defaults = (type => '');
%options = (%defaults, %options);
my $record = $self->get($key);
unless ($record)
{
if ($options{type})
{
$record = $self->new_record($key, {type => $options{type}})
or return 0;
}
else
{
return 0;
}
}
$record->set_prop($prop, $value)
or return 0;
return 1;
}
=item I<keys>
foreach my $key ($db->keys)
{
A simple convenience function to prevent having to access the config hash
inside the db object (technically private), or calling map across a get_all
call, which is what this is going to do. :)
This method returns a list of keys to the records in the db. It does not sort.
=cut
sub keys
{
my $self = shift;
return map { $_->{key} } $self->get_all;
}
=pod
=head2 migrate
Process the fragments in the migration queue for this database, using
processTemplate.
The defaults are loaded from /etc/e-smith/db/<dbname>/migrate by default, but
the environment variable ESMITH_DB_DEFAULTSDIR can be set to use a different
hierarchy if required.
The entries in "migrate" are perl fragments which will be evaluated and
so can munge anything they choose to. But, please be gentle :-)
So you could have
/etc/e-smith/db/configuration/migrate/sshd/access
which is a perl fragment which does something funky to migrate the access
property from some old value to some new value.
After running all the migration scripts, and reloading the DB's data into
its local cache, the private method _loadDefaults is called to set any
missing default values and any forced settings.
=cut
sub migrate
{
my ($self) = @_;
my $dbfile = basename( $self->{file} );
unless ($dbfile)
{
carp "migrate can't determine filename";
return undef;
}
my $defaults_dir = $ENV{ESMITH_DB_DEFAULTSDIR} || "/etc/e-smith/db";
my $dir = "$defaults_dir/$dbfile/migrate";
eval {
if ( -d $dir )
{
processTemplate(
{
MORE_DATA => { 'DB_FILENAME' => $dbfile },
TEMPLATE_PATH => '',
OUTPUT_TYPE => 'string',
TEMPLATE_EXPAND_QUEUE =>
[ $dir, "/etc/e-smith/templates-default" ]
}
);
$self->reload;
}
$self->_loadDefaults();
};
if ($@)
{
warn "Warning: Migration of $dbfile failed fatally: $@\n";
$self->set_error($@);
return FALSE;
}
return TRUE;
}
=pod
=head2 resetToDefaults
Reset all entries to their default values, if defaults exist. This calls
the internal method _loadDefaults with the forceReset flag set. It should
not be used lightly!!
=cut
sub resetToDefaults
{
my ($self) = @_;
$self->_loadDefaults(1);
}
=head2 record_has_defaults
Returns true if there are defaults or force directories for the
given record name
=cut
sub record_has_defaults
{
my ( $self, $name ) = @_;
my $dbfile = basename( $self->{file} );
unless ($dbfile)
{
carp "record_has_defaults can't determine filename";
return undef;
}
unless ($name)
{
carp "record_has_defaults can't determine record name";
return undef;
}
my $defaults_dir = $ENV{ESMITH_DB_DEFAULTSDIR} || "/etc/e-smith/db";
my $dir = "$defaults_dir/$dbfile";
return ( -d "$dir/defaults/$name" ) || ( -d "$dir/force/$name" );
}
=pod
=head2 _loadDefaults ($forceReset)
B<This is a private method.>
Load the default properties for a given database.
Caller can provide a flag to force resetting properties that already exist.
Any forced properties will be evaluated after setting the default properties.
The defaults are loaded from the following directories in order (the
environment variable ESMITH_DB_DEFAULTSDIR can be set to use a different
hierarchy if required):
/etc/e-smith/db/<dbname>/defaults
/etc/e-smith/db/<dbname>/force
Each of these directories is arranged as a set of subdirectories, with the
directory name equal to the key for the given database. With these
subdirectories are files, which are named by the properties of these
database keys.
The entries in "defaults" will be skipped if the existing key/property
already exists (unless the $forceReset argument is provided). These are
simple files, whose contents are the value to be used for that property.
The entries in "force" are always loaded into the given key/property.
These are again simple files, like "defaults".
To make this concrete, you might have:
/etc/e-smith/db/configuration/defaults/sshd/access
containing the single word "private", which would be the default. This
value would only be used if no "access" property existed, or the
$forceReset option is passed.
You can override both "defaults" and "migrate" with
/etc/e-smith/db/configuration/force/sshd/access
containing the single word "public" to force the value of that property.
=cut
sub _loadDefaults
{
my ( $self, $force ) = @_;
my $dbfile = basename( $self->{file} );
unless ($dbfile)
{
carp "_loadDefaults can't determine filename";
return undef;
}
my $defaults_dir = $ENV{ESMITH_DB_DEFAULTSDIR} || "/etc/e-smith/db";
my @propQueue =
( "$defaults_dir/$dbfile/defaults", "$defaults_dir/$dbfile/force", );
foreach my $dir (@propQueue)
{
# Always process the force dir
$force = 1 if ( $dir =~ /\/force$/ );
next unless opendir DH, $dir;
foreach my $key ( grep !/^\./, readdir DH )
{
if ( -d "$dir/$key" )
{
my %props = ();
my $rec = $self->get($key);
opendir DH2, "$dir/$key";
foreach my $prop ( grep !/^\./, readdir DH2 )
{
unless ($force)
{
next if ( $rec && defined $rec->prop($prop) );
}
open FH, "$dir/$key/$prop";
my $val = join "", (<FH>);
chomp $val;
$props{$prop} = $val;
close FH;
}
closedir DH2;
if ($rec)
{
$rec->merge_props(%props);
}
else
{
$rec = $self->new_record( $key, \%props );
}
}
else
{
warn "Found non-directory $key in $dir\n";
}
}
close DH;
}
return 1;
}
=pod
=head2 get_value_and_delete ($key)
Retrieve the value of the named key, return it, and delete the record.
If the key does not exist, it returns undef. This is normally called from
migration code.
=cut
sub get_value_and_delete
{
my ( $self, $key ) = @_;
my $ret;
my $rec = $self->get($key);
if ($rec)
{
$ret = $rec->value;
$rec->delete;
}
return $ret;
}
=pod
=head2 get_prop_and_delete ($key, $prop)
Retrieve the named property of the named key, return the value, and delete the
property from the record. Returns undef if the property or key does not exist.
This is normally called from migration code.
=cut
sub get_prop_and_delete
{
my ( $self, $key, $prop ) = @_;
my $ret;
my $rec = $self->get($key);
if ($rec)
{
$ret = $rec->prop($prop);
$rec->delete_prop($prop);
}
return $ret;
}
sub set_error
{
my $self = shift;
$Error = shift;
}
sub error
{
return $Error;
}
=back
=head1 AUTHOR
SME Server Developers <bugs@e-smith.com>
=cut
1;

View File

@@ -0,0 +1,157 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::DB::Record;
use strict;
use warnings;
use esmith::DB;
our $VERSION = sprintf '%d.%03d', q$Revision: 1.6 $ =~ /: (\d+).(\d+)/;
=head1 NAME
esmith::DB::Record - an individual record in an E-Smith database
=head1 SYNOPSIS
B<DO NOT USE THIS CLASS DIRECTLY!> use via esmith::DB.
my $key = $record->key;
my %properties = $record->props;
my $value = $record->prop($prop_key);
$record->set_prop($prop_key, $prop_val);
my $value = $record->delete_prop($prop_key);
$record->merge_props(%more_properties);
$record->reset_props(%new_properties);
$record->delete;
print $record->show;
=head1 DESCRIPTION
This class is a general interface to individual records in esmith::DB
databases. It should not be used directly, but rather esmith::DBs
should hand you esmith::DB::Record objects.
Each subclass of esmith::DB will also have to subclass and implement
an esmith::DB::Record subclass.
=head2 Virtual Methods
=over 4
=item B<key>
my $key = $record->key;
Returns the $key for this $record;
=item B<props>
my %properties = $record->props;
my $num_props = $record->props;
Returns a hash of all the properties for this $record. In scalar
context it will return the number of properties this $record has.
=item B<prop>
=item B<set_prop>
my $value = $record->prop($property);
$record->set_prop($property, $value);
Gets/sets the $value of the $property in this $record.
set_prop() will die if the database is read-only.
=item B<delete_prop>
my $value = $record->delete_prop($property);
Deletes a $property from the $record, returning the old $value.
delete_prop() will die if the database is read-only.
=item B<merge_props>
$record->merge_props(%properties);
Adds the %properties to the $records existing properties. Any new
keys will be added, any existing keys will be overwritten.
merge_props() will die if the database is read-only.
=item B<reset_props>
$record->reset_props(%properties);
Replaces the $record's properties with the contents of %properties.
Any old properties will be deleted.
reset_props() will die if the database is read-only.
=item B<delete>
$record->delete;
Deletes the $record from its database.
delete() will die if the database is read-only.
=back
=head2 Concrete methods
=over 4
=item B<show>
my $formatted = $record->show;
Returns the $record's key and properties in a nice, human readable
format suitable for printing.
=cut
sub show {
my($self) = shift;
my $out = $self->key."\n";
my %props = $self->props;
# Determine our longest key so we know how to format.
my $max_len = 0;
foreach (keys %props) { $max_len = length if length > $max_len }
# But don't go too far.
$max_len = 40 if $max_len > 40;
foreach my $prop (sort { $a cmp $b } keys %props) {
$out .= sprintf " %${max_len}s = %s\n", $prop, $props{$prop};
}
return $out;
}
=back
=head1 SEE ALSO
L<esmith::DB>
=cut
1;

View File

@@ -0,0 +1,660 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::DB::db;
use strict;
use warnings;
use Carp;
our $VERSION = sprintf '%d.%03d', q$Revision: 1.29 $ =~ /: (\d+).(\d+)/;
use esmith::db;
use esmith::config;
use esmith::DB::db::Record;
use esmith::DB;
our @ISA = qw(esmith::DB);
=for testing
use_ok('esmith::DB::db');
=head1 NAME
esmith::DB::db - interface to esmith::db databases
=head1 SYNOPSIS
I<Works just like an esmith::DB class except where noted>
=head1 DESCRIPTION
This module provides an abstracted interface to esmith::db flat-file
databases. It will read from and write to esmith::db files and can be
safely used right along side esmith::db. This follows the esmith::DB
interface and will work as documented there unless otherwise stated.
You should use this instead of esmith::db, and replace any existing
esmith::db code with this.
I<Note for esmith::db users> the old concept of a 'type' is now simply
another property.
my $type = $record->prop('type');
replaces db_get_type().
The $record returned by esmith::DB::db subclass is an esmith::DB::db::Record
subclass object. See the esmith::DB::db manpage for details on how it is used.
=head2 Methods
=over 4
=item B<create>
Puts its error on esmith::DB::db->error
=begin testing
$Scratch_Conf = '10e-smith-lib/scratch.conf';
unlink $Scratch_Conf;
$db = esmith::DB::db->create($Scratch_Conf);
END { unlink $Scratch_Conf }
isa_ok( $db, 'esmith::DB::db', 'create()' );
ok( -e $Scratch_Conf, 'created a new config file' );
ok(! esmith::DB::db->create($Scratch_Conf),
'create() wont walk over an existing config' );
like( esmith::DB::db->error, qr/^File exists/, ' right error message' );
=end testing
=cut
sub create
{
my ( $class, $file ) = @_;
$file = $class->_file_path($file);
my $self;
eval {
$self = $class->_init($file);
croak "File exists" if -e $file;
$self->{config} = $self->_get_config($file)
|| croak "Can't get the esmith::config object";
# touch the config file so it gets created immediately
open( FILE, ">>$file" )
or die "Failed to open $file for appending: $!\n";
close FILE;
# Migrate, and check for errors, propagating them if they happen.
unless ( $self->migrate() )
{
chomp $@;
$self->set_error($@);
return;
}
};
if ($@)
{
chomp $@;
$self->set_error($@);
return;
}
return $self;
}
=item B<open>
=for notes
There's currently no way to get the reason why from esmith::config.
=begin testing
unlink $Scratch_Conf;
ok( !esmith::DB::db->open($Scratch_Conf), 'open() on a non-existent db' );
is( esmith::DB::db->error, "File doesn't exist", ' right error' );
esmith::DB::db->create($Scratch_Conf);
$DB = esmith::DB::db->open($Scratch_Conf);
isa_ok( $DB, 'esmith::DB::db' );
=end testing
=cut
sub open
{
my ( $class, $file ) = @_;
$file = $class->_file_path($file);
my $self = $class->_init($file);
if ( -e $file && !-w $file )
{
$self->{ro} = 1;
}
return $self->_open($file) ? $self : undef;
}
=item B<open_local>
=for notes
There's currently no way to get the reason why from esmith::config.
=begin testing
unlink $Scratch_Conf;
ok( !esmith::DB::db->open_local($Scratch_Conf), 'open() on a non-existent db' );
is( esmith::DB::db->error, "File doesn't exist", ' right error' );
esmith::DB::db->create($Scratch_Conf);
$DB = esmith::DB::db->open_local($Scratch_Conf);
isa_ok( $DB, 'esmith::DB::db' );
=end testing
=cut
sub open_local
{
my ( $class, $file ) = @_;
$file = $class->_file_path($file);
my $self = $class->_init($file);
if ( -e $file && !-w $file )
{
$self->{ro} = 1;
}
return $self->_open($file) ? $self : undef;
}
=begin testing
ok( my $db = esmith::DB::db->open_ro($Scratch_Conf),
'open_ro on a non-existent db');
eval { $db->new_record('foo', { type => 'bar' }) };
like( $@, qr/^This DB is opened read-only/ );
=end testing
=cut
sub open_ro
{
my ( $class, $file ) = @_;
$file = $class->_file_path($file);
my $self = $class->_init($file);
$self->{ro} = 1;
return $self->_open($file) ? $self : undef;
}
=begin testing
ok( my $db = esmith::DB::db->open_ro_local($Scratch_Conf),
'open_ro on a non-existent db');
eval { $db->new_record('foo', { type => 'bar' }) };
like( $@, qr/^This DB is opened read-only/ );
=end testing
=cut
sub open_ro_local
{
my ( $class, $file ) = @_;
$file = $class->_file_path($file);
my $self = $class->_init($file);
$self->{ro} = 1;
return $self->_open($file) ? $self : undef;
}
sub is_ro
{
return $_[0]->{ro} ? 1 : 0;
}
sub _open
{
my ( $self, $file ) = @_;
eval {
# This is unfortunately not atomic, but I don't think
# that's a big deal.
die "File doesn't exist\n" unless -e $file;
die "File isn't readable\n" unless -r $file;
$self->{config} = $self->_get_config($file)
|| die "Can't get the esmith::config object";
};
if ($@)
{
chomp $@;
$self->set_error($@);
return;
}
return 1;
}
sub _get_config
{
my ( $self, $file ) = @_;
my %config;
tie %config, $self->tie_class, $file;
return \%config;
}
sub _init
{
my ( $class, $file ) = @_;
my $self = bless { file => $file }, $class;
return $self;
}
sub _file_path
{
my ( $class, $file ) = @_;
if ($file =~ m:/:)
{
use File::Basename;
warn "Deprecated pathname $file passed to _file_path()\n"
if dirname($file) eq "/home/e-smith";
return $file;
}
if (-e "/home/e-smith/db/$file")
{
return "/home/e-smith/db/$file";
} elsif (-e "/home/e-smith/$file") {
warn "Database found in old location /home/e-smith/$file";
return "/home/e-smith/$file";
} else {
return "/home/e-smith/db/$file";
}
}
=item B<as_hash>
=begin testing
use esmith::TestUtils qw(scratch_copy);
my $scratch = scratch_copy('10e-smith-lib/db_dummy.conf');
my %db = esmith::DB::db->as_hash($scratch);
my %expect = ( Foo => { type => 'Bar' },
Night => { type => 'Day' },
Squid => { type => 'cephalopod',
arms => 10,
species => 'Loligo' },
Pipe => { type => 'art',
pipe => 'this is not a | got that?',},
Haiku => { type => 'poem',
words =>
"Damian Conway\nGod damn! Damian Conway\nDamian Conway",
},
Octopus => { type => 'cephalopod',
arms => 8,
species => '',
}
);
is_deeply( \%db, \%expect );
%db = esmith::DB::db->open($scratch)->as_hash;
is_deeply( \%db, \%expect );
=end testing
=item B<reload>
=begin testing
my $db2 = esmith::DB::db->open($Scratch_Conf);
my $something = $DB->new_record('something', { type => "wibble" });
isa_ok( $something, 'esmith::DB::db::Record', 'new record in 1st DB' );
ok( !$db2->get('something'), ' 2nd DB still cant see new record' );
ok( $db2->reload, ' reload' );
ok( $db2->get('something'), ' 2nd DB can see new record' );
$something->delete;
=end testing
=cut
sub reload
{
my ($self) = shift;
$self->_open( $self->file );
}
=item B<file>
=for testing
is( $db->file, $Scratch_Conf, 'file()' );
=cut
sub file
{
my ($self) = shift;
return $self->{file};
}
=item B<new_record>
=begin testing
my $record = $DB->new_record('Big Brother', { year => 1984,
day => 'night',
type => 'Govt',
});
isa_ok( $record, 'esmith::DB::db::Record', 'new_record' );
is( $record->key, 'Big Brother', 'key' );
is( $record->prop('type'), 'Govt', 'type' );
is_deeply( {$record->props}, {year => 1984, day => 'night', type => 'Govt'},
'props' );
is( $record->prop('year'), 1984, 'prop() get' );
is( $record->prop('day'), 'night', 'prop() get again' );
$record = $DB->new_record('No props');
isa_ok( $record, 'esmith::DB::db::Record', 'new_record() w/o props' );
is( $record->key, 'No props', ' key' );
my $db2 = esmith::DB::db->open($DB->file);
ok( $db2->get('No props'), ' can be gotten' );
$record->delete;
=end testing
=cut
sub new_record
{
my ( $self, $key, $props ) = @_;
croak "This DB is opened read-only" if $self->is_ro;
if ( defined db_get( $self->{config}, $key ) )
{
return;
}
my $type = exists $props->{type} ? delete $props->{type} : '';
db_set( $self->{config}, $key, $type, $props );
$self->tie_class->_writeconf($self->{file}, $self->{config});
return esmith::DB::db::Record->_construct( $self, $key, $self->{config} );
}
=item B<get>
=begin testing
my $rec = $DB->get('Big Brother');
isa_ok( $rec, 'esmith::DB::db::Record', 'get' );
is( $rec->key, 'Big Brother', ' right key' );
=end testing
=cut
sub get
{
my ( $self, $key ) = @_;
unless ( defined db_get( $self->{config}, $key ) )
{
return;
}
return esmith::DB::db::Record->_construct( $self, $key, $self->{config} );
}
=item B<get_all>
=begin testing
$DB->new_record('Borg', { type => 'Govt', resistance => 'futile' });
my @records = $DB->get_all;
is( @records, 2, 'get_all' );
ok( !(grep { !$_->isa('esmith::DB::db::Record') } @records),
' theyre all records' );
=end testing
=cut
sub get_all
{
my ($self) = shift;
return
map { esmith::DB::db::Record->_construct( $self, $_, $self->{config} ) }
db_get( $self->{config} );
}
=item B<get_all_by_prop>
=begin testing
$DB->new_record('Pretz', { type => 'snack', flavor => 'old fashion' });
my @records = $DB->get_all_by_prop(type => 'Govt');
is( @records, 2, 'get_all_by_prop() type' );
ok( !(grep { $_->prop('type') ne 'Govt' } @records),
' theyre the right type' );
$DB->new_record('Pork lips', { type => 'snack', flavor => 'old fashion' });
@records = $DB->get_all_by_prop(flavor => 'old fashion');
is( @records, 2, 'get_all_by_prop()' );
ok( !(grep { $_->prop('flavor') ne 'old fashion' } @records),
' they have the right properties' );
=end testing
=cut
sub tie_class
{
return 'esmith::config';
}
sub close
{
}
=begin deprecated
=item B<list_by_type>
Given a type of item to look for in the database (eg "service", "ibay"),
returns a list of items which are that type. This is the underlying
routine behind esmith::AccountsDB::ibays() and similar methods.
=end deprecated
=for testing
ok($DB->list_by_type("Govt"), "list_by_type *deprecated*");
=cut
sub list_by_type
{
my ( $self, $type ) = @_;
return map $_->key, $self->get_all_by_prop( type => $type );
}
=back
=head1 EXAMPLE
The full docs can be found in esmith::DB and esmith::DB::Record, but
here's a cheat sheet for esmith::config and esmith::db users.
=over 4
=item opening the default config
use esmith::config
my %config;
tie %config, 'esmith::config;
Now:
use esmith::ConfigDB;
my $config = esmith::ConfigDB->open;
=item opening a specific config database
my %config;
tie %config, 'esmith::config', $config_file;
Now:
my $config = esmith::ConfigDB->open($config_file);
=item creating a new config database
This one's important. Before you could just tie esmith::config to any file
and it would create it for you. Now you have to explicitly create it.
my %config;
tie %config, 'esmith::config', $new_config_file;
Now:
my $config = esmith::ConfigDB->create($new_config_file);
=item checking if a record exists
print "Yep" if exists $config{foo};
now:
print "Yep" if $config->get('foo'); # unless of course, 'foo' is zero
=item creating a new record
Previously you could just create records on the fly:
# single value
$config{foo} = 'whatever';
# with properties
db_set(\%config, 'whatever', 'sometype', { with => 'properties' });
Now you have to explicitly create them:
# single value
my $foo = $config->new_record('foo');
$foo->set_value('foo');
# with properties
my %defaults = ( 'type' => 'sometype',
'linux' => 'stable',
'windows' => 'stable?' );
my $foo = $config->new_record('foo', \%defaults);
Note that 'type' is now just another property.
Here's a handy "create this if it doesn't already exist" idiom.
my $rec = $config->get($key) ||
$config->new_record($key);
=item getting a value
Entries in a database should no longer be thought of as values, but as
records.
my $val = $config{foo};
Now this only works with entries with single value. Things with
multiple properties are dealt with differently.
my $record = $config->get('foo');
my $val = $record->value;
=item setting a value
$config{foo} = 'something';
now
my $record = $config->get('foo');
$record->set_value('something');
=item getting a property
my $this = db_get_prop(\%config, 'foo', 'this');
now:
my $foo = $config->get('foo');
my $this = $foo->prop('this');
=item getting & setting properties
my $val = db_get_prop(\%config, 'foo', 'some prop');
db_set_prop(\%config, 'foo', 'some prop' => $new_val);
now:
my $val = $record->prop('some prop');
$record->set_prop('some prop' => $new_val);
=item get/setting the type
my $type = db_get_type(\%config, 'foo');
db_set_type(\%config, 'foo', $new_type);
type is now just a property
my $record = $db->get('foo');
my $type = $record->prop('type');
$record->set_prop('type', $new_type);
=item getting all the properties
my %props = db_get_prop(\%config, 'foo');
now
my %props = $record->props;
=back
=head1 AUTHOR
SME Server Developers <bugs@e-smith.com>
=head1 SEE ALSO
L<esmith::AccountsDB>, L<esmith::ConfigDB>, L<esmith::DB::db::Record>
=cut
1;

View File

@@ -0,0 +1,353 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::DB::db::Record;
use strict;
use warnings;
use Carp;
use esmith::db;
use esmith::DB::db;
require esmith::DB::Record;
our @ISA = qw(esmith::DB::Record);
=begin testing
use_ok('esmith::DB::db::Record');
use_ok('esmith::DB::db');
use File::Copy;
$Scratch_Conf = '10e-smith-lib/scratch.conf';
copy('10e-smith-lib/db_dummy.conf', $Scratch_Conf);
END { unlink $Scratch_Conf }
$DB = esmith::DB::db->open($Scratch_Conf);
$Squid = $DB->get('Squid');
=end testing
=head1 NAME
esmith::DB::db::Record - Individual records in an esmith::db database
=head1 SYNOPSIS
Unless otherwise noted, works just like esmith::DB::Record.
=head1 DESCRIPTION
This class represents entries in esmith::db flat-file database. A
single object is a single line.
This class is not useful by itself but rather they are handed out
via esmith::DB::db objects.
=begin protected
=head2 Protected Methods
These methods are only allowed to be called by esmith::DB::db classes.
=item B<_construct>
my $record = esmith::DB::db::Record->_construct($db, $key, $config);
Generates a new esmith::DB::db::Record representing data inside the
$db (an esmith::DB::db object).
This does *not* write anything into $db. This is here so a $db can
initialize a new Record from existing data.
=end protected
=cut
sub _construct {
my($class, $db, $key, $config) = @_;
die "_construct may only be called by esmith::DB::db"
unless caller->isa('esmith::DB::db');
my $self = {
db => $db,
config => $config,
key => $key
};
return bless $self, $class;
}
=head2 Methods
=over 4
=item B<key>
=for testing
is( $Squid->key, 'Squid', 'key()' );
=cut
sub key {
my($self) = shift;
return $self->{key};
}
=item B<props>
=for testing
is_deeply( {$Squid->props}, {arms => 10, species => 'Loligo',
type => 'cephalopod'}, 'props()' );
=cut
sub props {
my($self) = shift;
my %props = db_get_prop($self->{config}, $self->{key});
$props{type} = db_get_type($self->{config}, $self->{key});
foreach my $prop (keys %props) {
$props{$prop} =~ s{\\\|}{\|}g if $props{$prop};
}
return wantarray ? %props : keys %props;
}
=item B<prop>
=item B<set_prop>
=begin testing
is( $Squid->prop('arms'), 10, 'prop()' );
$Squid->set_prop('arms', 1000);
is( $Squid->prop('arms'), 1000, 'set_prop()' );
is( $Squid->prop('type'), 'cephalopod', 'prop() type get' );
$Squid->set_prop('type', 'tree dweller');
is( $Squid->prop('type'), 'tree dweller', 'set_prop() type set' );
$Squid->set_prop('bar', 'foo | bar');
is( $Squid->prop('bar'), 'foo bar', 'prop/set_prop with pipes - pipe stripped' );
{
my $warning = '';
local $SIG{__WARN__} = sub { $warning = join '', @_ };
$Squid->prop('bar', 'foo');
like( $warning, qr/^prop\(\) got extra arguments 'foo'. Maybe you ment set_prop\(\)\?/, 'prop()/set_prop() mixup warns' );
$warning = '';
is( $Squid->prop('I_dont_exist'), undef, 'prop() on non-existent prop' );
is( $warning, '', ' no warning' );
$warning = '';
$Squid->set_prop('I_dont_exist', undef);
is( $Squid->prop('I_dont_exist'), '', 'set_prop() with undef value' );
is( $warning, '', ' no warning' );
$Squid->delete_prop('I_dont_exist');
}
=end testing
=cut
sub prop {
my($self, $property) = splice @_, 0, 2;
warn sprintf "prop() got extra arguments '%s'. Maybe you ment set_prop()?",
"@_" if @_;
my $value;
if( $property eq 'type' ) {
$value = db_get_type($self->{config}, $self->{key});
}
else {
$value = db_get_prop($self->{config}, $self->{key}, $property);
}
# Unescape escaped pipes. esmith::db can't do this for us.
$value =~ s{\\\|}{\|}g if defined $value;
return $value;
}
sub set_prop {
my($self, $property, $value) = @_;
croak "The DB is open read-only" if $self->{db}->is_ro;
# Strip pipes - we can't safely escape them while some code
# still expects to split on pipe
$value =~ s{\|}{}g if defined $value;
my $ret;
if( $property eq 'type' ) {
$ret = db_set_type($self->{config}, $self->{key}, $value);
}
else {
$ret = db_set_prop($self->{config}, $self->{key},
$property => $value);
}
return $ret;
}
=item B<delete_prop>
A special case for esmith::DB::db::Record, you're not allowed to
delete the 'type' property.
=for testing
is( $Squid->delete_prop('species'), 'Loligo',
'delete_prop() returns the old value' );
is_deeply( {$Squid->props}, {arms => 1000, bar => 'foo bar',
type => 'tree dweller' },
' and deletes' );
=cut
sub delete_prop {
my($self, $property) = @_;
croak "The DB is open read-only" if $self->{db}->is_ro;
croak "You're not allowed to delete a type from an esmith::DB::db::Record"
if $property eq 'type';
my $val = $self->prop($property);
db_delete_prop($self->{config}, $self->{key}, $property);
return $val;
}
=item B<merge_props>
=begin testing
my $octopus = $DB->get('Octopus');
$octopus->merge_props( arms => '8 + 2i', name => 'Fluffy', pipe => 'not |');
is_deeply( {$octopus->props}, { arms => '8 + 2i', type => 'cephalopod',
species => '', name => 'Fluffy',
pipe => 'not ' }, 'merge_props()' );
$octopus->merge_props( type => 'foo' );
is_deeply( {$octopus->props}, { arms => '8 + 2i', type => 'foo',
species => '', name => 'Fluffy',
pipe => 'not ' }, ' with type' );
$octopus->merge_props( { type => 'foo' } );
like( $_STDERR_, qr/^merge_props\(\) was accidentally passed a hash ref/m,
' anti-hash ref protection');
=end testing
=cut
sub merge_props {
my($self, %new_props) = @_;
croak "The DB is open read-only" if $self->{db}->is_ro;
if( ref $_[1] ) {
carp("merge_props() was accidentally passed a hash ref");
}
my %props = $self->props;
my %merged_props = (%props, %new_props);
# Strip out pipes.
foreach my $prop (keys %merged_props) {
$merged_props{$prop} =~ s{\|}{}g
if defined $merged_props{$prop};
}
my $type = delete $merged_props{type};
db_set($self->{config}, $self->{key}, $type, \%merged_props);
}
=item B<reset_props>
=begin testing
my $octopus = $DB->get('Octopus');
eval { $octopus->reset_props( { type => 'foo' } ); };
like( $_STDERR_, qr/^reset_props\(\) was accidentally passed a hash ref/m,
' anti-hash ref protection');
$octopus->reset_props( arms => 8, name => 'Rupert', type => 'foo' );
is_deeply( {$octopus->props}, { arms => '8', name => 'Rupert',
type => 'foo' }, 'reset_props' );
eval { $octopus->reset_props( arms => '8 + 2i', name => 'Fluffy',
pipe => 'not ') };
like( $@, qr/^You must have a type property/, ' you must have a type');
=end testing
=cut
sub reset_props {
my($self, %new_props) = @_;
croak "The DB is open read-only" if $self->{db}->is_ro;
if( ref $_[1] ) {
carp("reset_props() was accidentally passed a hash ref");
}
die "You must have a type property" unless $new_props{type};
# Strip out pipes
foreach my $prop (keys %new_props) {
$new_props{$prop} =~ s{\|}{}g
if defined $new_props{$prop};
}
my $type = delete $new_props{type} || $self->prop('type');
db_set($self->{config}, $self->{key}, $type, \%new_props);
}
=item B<delete>
=for testing
my $foo = $DB->get('Foo');
$foo->delete;
ok( !$DB->get('Foo'), 'delete()' );
=cut
sub delete {
my($self) = shift;
croak "The DB is open read-only" if $self->{db}->is_ro;
db_delete($self->{config}, $self->{key});
}
=item B<show>
=begin testing
is( $Squid->show, <<SQUID, 'show' );
Squid
arms = 1000
bar = foo bar
type = tree dweller
SQUID
=end testing
=back
=head1 SEE ALSO
L<esmith::DB::db>
=cut
1;

View File

@@ -0,0 +1,99 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::DomainsDB;
use strict;
use warnings;
use esmith::DB::db;
our @ISA = qw( esmith::DB::db );
=head1 NAME
esmith::DomainsDB - interface to esmith domains database
=head1 SYNOPSIS
use esmith::DomainsDB;
my $c = esmith::DomainsDB->open;
# everything else works just like esmith::DB::db
=head1 DESCRIPTION
This module provides an abstracted interface to the esmith domain
database.
Unless otherwise noted, esmith::DomainsDB acts like esmith::DB::db.
=cut
=head2 open()
Like esmith::DB->open, but if given no $file it will try to open the
file in the ESMITH_DOMAINS_DB environment variable or domains.
=begin testing
use_ok("esmith::DomainsDB");
$C = esmith::DomainsDB->open('10e-smith-lib/domains.conf');
isa_ok($C, 'esmith::DomainsDB');
is( $C->get("test")->prop('foo'), "bar",
"We can get stuff from the db");
=end testing
=cut
sub open {
my ($self, $file) = @_;
$file = $file || $ENV{ESMITH_DOMAINS_DB} || "domains";
return $self->SUPER::open($file);
}
=head2 open_ro()
Like esmith::DB->open_ro, but if given no $file it will try to open the
file in the ESMITH_DOMAINS_DB environment variable or domains.
=begin testing
=end testing
=cut
sub open_ro {
my($class, $file) = @_;
$file = $file || $ENV{ESMITH_DOMAINS_DB} || "domains";
return $class->SUPER::open_ro($file);
}
=for testing
$C = esmith::DomainsDB->open('10e-smith-lib/domains.conf');
isa_ok($C, 'esmith::DomainsDB');
can_ok($C, 'domains');
can_ok($C, 'get_all_by_prop');
is(scalar($C->domains()), 2, "Found 2 domains with domains()");
=cut
sub domains {
my ($self) = @_;
return $self->get_all_by_prop(type => 'domain');
}
=head1 AUTHOR
SME Server Developers <bugs@e-smith.com>
=head1 SEE ALSO
L<esmith::ConfigDB>
=cut
1;

View File

@@ -0,0 +1,230 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::HostsDB;
use strict;
use warnings;
use esmith::DB::db;
our @ISA = qw( esmith::DB::db );
=head1 NAME
esmith::HostsDB - interface to esmith hostnames/addresses database
=head1 SYNOPSIS
use esmith::HostsDB;
my $hosts = esmith::HostsDB->open;
# everything else works just like esmith::DB::db
# these methods are added
my @hosts = $hosts->hosts;
my @new_hosts = $hosts->propogate_hosts;
=head1 DESCRIPTION
This module provides an abstracted interface to the esmith hosts
database.
Unless otherwise noted, esmith::HostsDB acts like esmith::DB::db.
=cut
=head2 Overridden methods
=over 4
=item I<open>
Like esmith::DB->open, but if given no $file it will try to open the
file in the ESMITH_HOSTS_DB environment variable or hosts.
=begin testing
use_ok("esmith::HostsDB");
$H = esmith::HostsDB->open('10e-smith-lib/hosts.conf');
isa_ok($H, 'esmith::HostsDB');
is( $H->get("otherhost.mydomain.xxx")->prop('InternalIP'), "192.168.1.3",
"We can get stuff from the db");
=end testing
=cut
sub open {
my($class, $file) = @_;
$file = $file || $ENV{ESMITH_HOSTS_DB} || "hosts";
return $class->SUPER::open($file);
}
=head2 open_ro()
Like esmith::DB->open_ro, but if given no $file it will try to open the
file in the ESMITH_HOSTS_DB environment variable or hosts.
=begin testing
=end testing
=cut
sub open_ro {
my($class, $file) = @_;
$file = $file || $ENV{ESMITH_HOSTS_DB} || "hosts";
return $class->SUPER::open_ro($file);
}
=back
=head2 Additional Methods
These methods are added be esmith::HostsDB
=over 4
=item I<hosts>
my @hosts = $hosts->hosts;
Returns a list of all host records in the database.
=begin testing
$db = esmith::HostsDB->open('10e-smith-lib/hosts.conf');
isa_ok($db, 'esmith::HostsDB');
can_ok($db, 'hosts');
my @hosts = $db->hosts();
isnt( @hosts, 0 );
is_deeply(\@hosts, [$db->get_all_by_prop('type' => 'host')]);
=end testing
=cut
sub hosts {
my ($self) = @_;
return $self->get_all_by_prop('type' => 'host');
}
=item I<propogate_hosts>
my @new_hosts = $hosts->propogate_hosts($old_name, $new_name);
When the name of your e-smith machine changes, this will change the
name of any hosts which also started with $old_name to use the
$new_name.
Returns a list of the newly tranlsated host records.
=begin testing
use esmith::ConfigDB;
my $hosts_file = '10e-smith-lib/propogate_hosts.conf';
END { unlink $hosts_file }
my $db = esmith::HostsDB->create($hosts_file);
use esmith::TestUtils qw(scratch_copy);
my $c_scratch = scratch_copy('10e-smith-lib/configuration.conf');
my $config = esmith::ConfigDB->open($c_scratch);
isa_ok($config, 'esmith::ConfigDB');
my $name = $config->get('SystemName')->value;
# setup some dummy hosts to propogate.
foreach my $host ( "$name.tofu-dog.com", "$name.wibble.org",
"wibble.$name.org", "yarrow.hack" )
{
$db->new_record($host, { type => 'host', HostType => 'Self',
ExternalIP => '', InternalIP => ''
});
}
$db->reload;
my @new_hosts = $db->propogate_hosts($name, "armondo");
my @hosts = $db->hosts;
is( @hosts, 4 );
is_deeply( [sort map { $_->key } @hosts],
[sort +('armondo.tofu-dog.com',
'armondo.wibble.org',
"wibble.$name.org",
'yarrow.hack',
)]
);
is( @new_hosts, 2 );
is_deeply( [sort map { $_->key } @new_hosts],
[sort qw(armondo.tofu-dog.com armondo.wibble.org)]
);
=end testing
=cut
sub propogate_hosts
{
my($self, $old_name, $new_name) = @_;
my @new_hosts = ();
foreach my $host ($self->hosts)
{
my $new_host = $host->key;
if( $new_host =~ s/^\Q$old_name.\E/$new_name./ )
{
push @new_hosts, $self->new_record($new_host,
{ $host->props }
);
$host->delete;
}
}
return @new_hosts;
}
=head2 $db->get_hosts_by_domain
Given a domain name (as a string), finds any hosts which match it and
return them as a list of record objects.
=begin testing
my $h = esmith::HostsDB->open('10e-smith-lib/hosts.conf');
my @hosts = $h->get_hosts_by_domain('otherdomain.xxx');
is(scalar(@hosts), 2, "Found two hosts in otherdomain.xxx");
isa_ok($hosts[0], 'esmith::DB::Record');
=end testing
=cut
sub get_hosts_by_domain {
my ($self, $domain) = @_;
my @all = $self->get_all();
my @return;
foreach my $h (@all) {
push @return, $h if $h->key() =~ /^[^\.]+\.$domain$/;
}
return @return;
}
=back
=head1 AUTHOR
SME Server Developers <bugs@e-smith.com>
=head1 SEE ALSO
L<esmith::ConfigDB>
=cut
1;

View File

@@ -0,0 +1,331 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::I18N;
use strict;
use esmith::ConfigDB;
use POSIX qw(setlocale LC_ALL LC_CTYPE);
use Locale::gettext;
use I18N::AcceptLanguage;
use I18N::LangTags qw(is_language_tag locale2language_tag);
=pod
=head1 NAME
esmith::I18N - Internationalization utilities Mitel Network SME Server
=head1 VERSION
This file documents C<esmith::I18N> version B<1.4.0>
=head1 SYNOPSIS
use esmith::I18N;
my $i18n = new esmith::I18N;
=head1 DESCRIPTION
This module provides general internationalization and localisation
utilities for developers of the Mitel Networks SME Server.
=begin testing
use I18N::LangTags qw(is_language_tag locale2language_tag language_tag2locale);
use_ok('esmith::I18N');
=end testing
=head1 GENERAL UTILITIES
=cut
sub new
{
my $self = shift;
my $class = ref($self) || $self;
my %args = @_;
return $self;
}
=head2 availableLocales()
Returns an array containing the available locales supported by the
server.
=begin testing
$ENV{ESMITH_CONFIG_DB}="10e-smith-lib/sysconfig-en_US.conf";
$ENV{ESMITH_I18N_USRSHARELOCALE}="10e-smith-lib/usr/share/locale";
my $i18n = new esmith::I18N;
my @locales = grep !/CVS/, sort $i18n->availableLocales;
# NOTE: de is not a valid locale for the test - no server-console file
is_deeply(\@locales, [('en_US', 'fr_CA', 'wx_YZ')], "Locales match" );
=end testing
=cut
sub availableLocales()
{
my ($self) = shift;
my $localedir = $ENV{ESMITH_I18N_USRSHARELOCALE} || '/usr/share/locale';
return () unless opendir LOCALE, $localedir;
my @locales;
foreach my $locale ( grep(!/\./, readdir LOCALE) )
{
push @locales, $locale if
(-f "$localedir/$locale/LC_MESSAGES/server-console.mo" or
-f "$localedir/$locale/LC_MESSAGES/server-console.po");
}
closedir LOCALE;
return @locales;
}
=head2 fallbackLocale()
Return system fallback locale
=cut
sub fallbackLocale()
{
return "en_US.utf8";
}
=head2 preferredLocale()
Retrieves the preferred locale for this server.
=begin testing
$ENV{ESMITH_CONFIG_DB}="10e-smith-lib/sysconfig-en_US.conf";
my $i18n = new esmith::I18N;
is($i18n->preferredLocale, 'en_US', "en_US.conf: Preferred locale is en_US");
$ENV{ESMITH_CONFIG_DB}="10e-smith-lib/sysconfig-fr_CA.conf";
$i18n = new esmith::I18N;
is($i18n->preferredLocale, 'fr_CA', "fr_CA.conf: Preferred locale is fr_CA");
=end testing
=cut
sub preferredLocale()
{
my ($self) = shift;
my $db = esmith::ConfigDB->open_ro || return $self->fallbackLocale;
my ($locale, @rest) = $db->getLocale();
return $locale || $self->fallbackLocale;
}
=head2 setLocale()
Configure the locale for gettext() for the supplied text domain.
The method takes two arguments, the text domain, and an optional argument
which can be either a language tag or a locale.
=cut
sub setLocale()
{
my ($self, $text_domain, $opt) = @_;
my $locale;
$locale = $self->langtag2locale($opt) if ($opt);
$locale ||= $self->preferredLocale;
$locale =~ s{(?:\..*)?$}{.utf8};
$ENV{'LANGUAGE'} = $locale;
$ENV{'LANG'} = $ENV{'LANGUAGE'};
setlocale(LC_MESSAGES, $locale);
setlocale(LC_MESSAGES, $locale);
setlocale(LC_ALL, $locale);
setlocale(LC_ALL, $locale);
bindtextdomain ($text_domain, "/usr/share/locale");
textdomain ($text_domain);
}
=head2 langtag2locale
Even though the directories appear in /usr/share/locale, they also need
to appear in /usr/lib/locale to actually be treated as locales. Read the
Perl locale docs for details of how horrid this is. For now, we're just
going to force things for supported languages.
=begin testing
my $i18n = new esmith::I18N;
is($i18n->langtag2locale("en"), "en_US", "en langtag is en_US locale");
is($i18n->langtag2locale("en-us"), "en_US", "en-us langtag is en_US locale");
is($i18n->langtag2locale("en-au"), "en_AU", "en-au langtag is en_AU locale");
is($i18n->langtag2locale("es"), "es_ES", "es langtag is es_ES locale");
is($i18n->langtag2locale("es-es"), "es_ES", "es-es langtag is es_ES locale");
is($i18n->langtag2locale("es-ar"), "es_AR", "es-ar langtag is es_AR locale");
is($i18n->langtag2locale("fr"), "fr_CA", "fr langtag is fr_CA locale");
is($i18n->langtag2locale("fr-ca"), "fr_CA", "fr-ca langtag is fr_CA locale");
is($i18n->langtag2locale("fr-fr"), "fr_FR", "fr-fr langtag is fr_FR locale");
=end testing
=cut
sub langtag2locale
{
my ($self, $opt) = @_;
my $locale;
if (is_language_tag($opt))
{
$locale = _language_tag2locale($opt) || $self->fallbackLocale;
unless (-d "/usr/lib/locale/$locale")
{
$locale = "da_DK" if ($opt =~ /^da(-.*)?/);
$locale = "de_DE" if ($opt =~ /^de(-.*)?/);
$locale = "el_GR" if ($opt =~ /^el(-.*)?/);
$locale = "en_US" if ($opt =~ /^en(-.*)?/);
$locale = "es_ES" if ($opt =~ /^es(-.*)?/);
$locale = "fr_CA" if ($opt =~ /^fr(-.*)?/);
$locale = "hu_HU" if ($opt =~ /^hu(-.*)?/);
$locale = "id_ID" if ($opt =~ /^id(-.*)?/);
$locale = "it_IT" if ($opt =~ /^it(-.*)?/);
$locale = "nl_NL" if ($opt =~ /^nl(-.*)?/);
$locale = "pt_BR" if ($opt =~ /^pt(-.*)?/);
$locale = "sl_SL" if ($opt =~ /^sl(-.*)?/);
$locale = "sv_SE" if ($opt =~ /^sv(-.*)?/);
}
}
else
{
$locale = $opt;
}
return $locale;
}
sub _language_tag2locale
{
my $langtags = $_[0];
my @locales;
foreach my $maybe (split /[\n\r\t ,]+/, $langtags)
{
push @locales,
lc($1) . ( $2 ? ('_' . uc($2)) : '' )
if $maybe =~ m/^([a-zA-Z]{2})(?:-([a-zA-Z]{2}))?$/s;
}
return $locales[0] unless wantarray; # might be undef!
return @locales; # might be empty!
}
=head2 availableLanguages()
Returns an array containing the available languages supported by the
server.
=begin testing
$ENV{ESMITH_CONFIG_DB}="10e-smith-lib/sysconfig-en_US.conf";
$ENV{ESMITH_I18N_ESMITHLOCALEDIR}="10e-smith-lib/etc/e-smith/locale";
my $i18n = new esmith::I18N;
my @locales = grep !/CVS/, sort $i18n->availableLanguages;
is_deeply(\@locales, [('en-us', 'es', 'fr-ca', 'jk', 'wx-yz')], "Locales match" );
=end testing
=cut
sub availableLanguages
{
my ($self) = shift;
my $localedir = $ENV{ESMITH_I18N_ESMITHLOCALEDIR} || '/etc/e-smith/locale';
return () unless opendir LOCALE, $localedir;
my @locales = grep(!/\./, readdir LOCALE);
closedir LOCALE;
return @locales;
}
=head2 fallbackLanguage()
Return system fallback language
=cut
sub fallbackLanguage()
{
return "en-us";
}
=head2 preferredLanguage()
Returns the preferred language, determined by the HTTP_ACCEPT_LANGUAGE
setting from the browser and the available languages on the server.
=begin testing
my $i18n = new esmith::I18N;
delete $ENV{HTTP_ACCEPT_LANGUAGE};
is( $i18n->preferredLanguage(), "en-us", "Preferred language is en-us");
is( $i18n->preferredLanguage("en-us"), "en-us", "Preferred language is en-us");
is( $i18n->preferredLanguage("en-us, fr-ca"), "en-us", "Preferred language is en-us");
is( $i18n->preferredLanguage("fr-ca, en-us"), "fr-ca", "Preferred language is fr-ca");
$ENV{HTTP_ACCEPT_LANGUAGE} = "de, es";
is( $i18n->preferredLanguage(), "es", "Preferred language is es");
$ENV{HTTP_ACCEPT_LANGUAGE} = "de, fr-ca, es, en-us";
is( $i18n->preferredLanguage(), "fr-ca", "Preferred language is fr-ca");
$ENV{HTTP_ACCEPT_LANGUAGE} = "de, es, fr-ca, en-us";
is( $i18n->preferredLanguage(), "es", "Preferred language is es");
=end testing
=cut
sub preferredLanguage
{
my ($self, $browser_languages) = @_;
$browser_languages ||= $ENV{HTTP_ACCEPT_LANGUAGE}
||= $self->fallbackLanguage;
my @availableLanguages = $self->availableLanguages;
my $acceptor = I18N::AcceptLanguage->new();
my $language = $acceptor->accepts($browser_languages, \@availableLanguages)
|| $self->fallbackLanguage;
}
1;

View File

@@ -0,0 +1,77 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::Logger;
use Sys::Syslog qw(:DEFAULT setlogsock);
=head1 NAME
esmith::Logger - A filehandle abstraction around Syslog.
=head1 SYNOPSIS
use esmith::Logger;
tie *FH, 'esmith::Logger';
print FH "log message";
close FH;
=head1 DESCRIPTION
=cut
our $VERSION = sprintf '%d.%03d', q$Revision: 1.100 $ =~ /: (\d+).(\d+)/;
sub TIEHANDLE
{
my $class = ref($_[0]) || $_[0]; shift;
my $self;
my $title = shift || 'e-smith';
setlogsock 'unix';
openlog($title, 'pid', 'local1');
return bless \$self, $class;
}
sub PRINT
{
my $self = shift;
syslog('info', "%s", "@_");
}
sub PRINTF
{
my $self = shift;
my $fmt = shift;
syslog('info', $fmt, @_);
}
sub WRITE
{
die "Sorry, WRITE unimplemented.\n";
}
sub READ
{
die "Can't read from logger.\n";
}
sub READLINE
{
die "Can't read from logger.\n";
}
sub GETC
{
die "Can't read from logger.\n";
}
sub CLOSE
{
closelog();
}
1;

View File

@@ -0,0 +1,16 @@
#----------------------------------------------------------------------
# Copyright 1999-2008 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::NavigationDB;
use strict;
use warnings;
use esmith::ConfigDB::UTF8;
our @ISA = qw( esmith::ConfigDB::UTF8 );
1;

View File

@@ -0,0 +1,157 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::NetworksDB;
use strict;
use warnings;
use esmith::DB::db;
our @ISA = qw( esmith::DB::db );
=head1 NAME
esmith::NetworksDB - interface to esmith networks database
=head1 SYNOPSIS
use esmith::NetworksDB;
my $c = esmith::NetworksDB->open;
# everything else works just like esmith::DB::db
=head1 DESCRIPTION
This module provides an abstracted interface to the esmith master
configuration database.
Unless otherwise noted, esmith::NetworksDB acts like esmith::DB::db.
=cut
=head2 open()
Like esmith::DB->open, but if given no $file it will try to open the
file in the ESMITH_NETWORKS_DB environment variable or networks.
=begin testing
use_ok("esmith::NetworksDB");
$C = esmith::NetworksDB->open('10e-smith-lib/networks.conf');
isa_ok($C, 'esmith::NetworksDB');
is( $C->get("10.0.0.0")->prop('Mask'), "255.255.255.0",
"We can get stuff from the db");
=end testing
=cut
sub open
{
my ( $class, $file ) = @_;
$file = $file || $ENV{ESMITH_NETWORKS_DB} || "networks";
return $class->SUPER::open($file);
}
=head2 open_ro()
Like esmith::DB->open_ro, but if given no $file it will try to open the
file in the ESMITH_NETWORKS_DB environment variable or networks.
=begin testing
=end testing
=cut
sub open_ro
{
my ( $class, $file ) = @_;
$file = $file || $ENV{ESMITH_NETWORKS_DB} || "networks";
return $class->SUPER::open_ro($file);
}
=head2 networks
Return a list of all objects of type "network".
=cut
sub networks {
my ($self) = @_;
return $self->get_all_by_prop(type => 'network');
}
=head2 local_access_spec ([$access])
Compute the network/netmask entries which are to treated as local access.
There is also an optional access parameter which can further restrict
the values returned. If C<access> is C<localhost>, this routine will only
return a single value, equating to access from localhost only.
If called in scalar context, the returned string is suitable for
use in /etc/hosts.allow, smb.conf and httpd.conf, for example:
127.0.0.1 192.168.1.1/255.255.255.0
Note: The elements are space separated, which is suitable for use in
hosts.allow, smb.conf and httpd.conf. httpd.conf does not permit
comma separated lists in C<allow from> directives. Each element is either
an IP address, or a network/netmask string.
If called in list context, returns the array of addresses and network/netmask
strings. It's trivial, of course, to convert an array to a comma separated
list :-)
=cut
sub local_access_spec
{
my $self = shift;
my $access = shift || "private";
my @localAccess = ("127.0.0.1");
if ( $access eq "localhost" )
{
# Nothing more to do
}
elsif ( $access eq "private" )
{
foreach my $network ( $self->networks )
{
my $element = $network->key;
my $mask = $network->prop('Mask');
$element .= "/$mask" unless ($mask eq "255.255.255.255");
push @localAccess, $element;
}
}
elsif ( $access eq "public" )
{
@localAccess = ("ALL");
}
else
{
warn "local_access_spec: unknown access value $access\n";
}
return wantarray ? @localAccess : "@localAccess";
}
=head1 AUTHOR
SME Server Developers <bugs@e-smith.com>
=head1 SEE ALSO
L<esmith::DB::db>
L<esmith::DB::Record>
=cut
1;

View File

@@ -0,0 +1,480 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::cgi;
use strict;
use esmith::config;
use esmith::db;
use esmith::util;
BEGIN
{
}
=pod
=head1 NAME
esmith::cgi - Useful CGI routines for e-smith server and gateway
=head1 VERSION
This file documents C<esmith::cgi> version B<1.4.0>
=head1 SYNOPSIS
use esmith::cgi;
=head1 DESCRIPTION
This module contains a collection of useful routines for working with
the e-smith manager's CGI interface.
=head1 WEB PAGE HEADER GENERATION ROUTINES
=head2 genHeaderNonCacheable($q, $confref, $title)
=cut
sub genHeaderNonCacheable
{
my ($q, $confref, $title) = @_;
genHeader ($q, $confref, $title, '-20y', 1);
}
=pod
=head2 genHeaderCacheableNoPasswordCheck($q, $confref, $title)
=cut
sub genHeaderCacheableNoPasswordCheck
{
my ($q, $confref, $title) = @_;
genHeader ($q, $confref, $title, '+1d', 0);
}
=pod
=head2 genHeaderCacheableNoPasswordCheck($q, $confref, $title)
=cut
sub genHeaderNonCacheableNoPasswordCheck
{
my ($q, $confref, $title) = @_;
genHeader ($q, $confref, $title, '-20y', 0);
}
=pod
=head2 genHeader($q, $confref, $title, $expiry, $checkpassword)
=cut
sub genHeader
{
my ($q, $confref, $title, $expiry, $checkpassword) = @_;
print $q->header (-EXPIRES => $expiry, charset => 'UTF-8');
genHeaderStartHTML ($q, "panel_main");
print $q->h1 ($title);
}
=pod
=head2 genNavigationHeader($q)
=cut
sub genNavigationHeader
{
my ($q, $num) = @_;
print $q->header (-EXPIRES => '-20y', charset => 'UTF-8');
genHeaderStartHTML ($q, "panel_nav", $num);
}
=pod
=head2 genNoframesHeader($q)
=cut
sub genNoframesHeader
{
my ($q) = @_;
print $q->header (-EXPIRES => '-20y', charset => 'UTF-8');
genHeaderStartHTML ($q, "panel_main");
}
=pod
=head2 genHeaderStartHTML($q)
=cut
sub genHeaderStartHTML
{
my ($q, $page_type, $num) = @_;
my ($cssFile);
my ($bodyStyle);
my ($script) = "//This swaps the class of the selected item.\n"
."function swapClass(){\n"
."var i,x,tB,j=0,tA=new Array(),arg=swapClass.arguments;\n"
."if(document.getElementsByTagName){for(i=4;i<arg.length;i++){tB=document.getElementsByTagName(arg[i]);\n"
."for(x=0;x<tB.length;x++){tA[j]=tB[x];j++;}}for(i=0;i<tA.length;i++){\n"
."if(tA[i].className){if(tA[i].id==arg[1]){if(arg[0]==1){\n"
."tA[i].className=(tA[i].className==arg[3])?arg[2]:arg[3];}else{tA[i].className=arg[2];}\n"
."}else if(arg[0]==1 && arg[1]=='none'){if(tA[i].className==arg[2] || tA[i].className==arg[3]){\n"
."tA[i].className=(tA[i].className==arg[3])?arg[2]:arg[3];}\n"
."}else if(tA[i].className==arg[2]){tA[i].className=arg[3];}}}}}\n";
if ($page_type eq "panel_nav") {
$cssFile = "sme_menu.css";
$bodyStyle = "menu"
}
elsif ($page_type eq "panel_main") {
$cssFile = "sme_main.css";
$bodyStyle = "main"
}
# the -CLASS thing gets sent as a body class, not in the header
print $q->start_html (-TITLE => 'SME Server server manager',
-META => {'copyright' => 'Copyright 1999-2006 Mitel Networks Corporation, Copyright (C) ____COPYYEARS____ Koozali Foundation, Inc.'},
-SCRIPT => "$script",
-CLASS => "$bodyStyle",
-STYLE => {
-code => '@import url("/server-common/css/'.$cssFile.'");',
-src => '/server-common/css/sme_core.css'
});
}
=pod
=head1 WEB PAGE FOOTER GENERATION ROUTINES
=head2 genFooter($q)
=cut
sub genFooter
{
my ($q) = @_;
if ($q->isa('CGI::FormMagick'))
{
print $q->parse_template("/etc/e-smith/web/common/foot.tmpl");
return;
}
my $release = esmith::util::determineRelease();
print $q->p
($q->hr ({-CLASS => "sme-copyrightbar"}),
$q->div ({-CLASS => "sme-copyright"},
"SME Server server ${release}<BR>" .
"Copyright 1999-2006 Mitel Networks Corporation, Copyright (C) ____COPYYEARS____ Koozali Foundation, Inc..<BR>" .
"All rights reserved.")
);
print '</DIV>';
print $q->end_html;
}
=pod
=head2 genFooterNoCopyright($q)
=cut
sub genFooterNoCopyright
{
my ($q) = @_;
print $q->p ($q->hr);
print $q->end_html;
}
=pod
=head2 genNavigationFooter($q)
=cut
sub genNavigationFooter
{
my ($q) = @_;
print $q->end_html;
}
=pod
=head2 genNoframesFooter($q)
=cut
sub genNoframesFooter
{
my ($q) = @_;
print $q->end_html;
}
=pod
=head1 FONT ROUTINES
=head2 curFont()
Returns the preferred font faces eg. "Verdana, Arial, Helvetica, sans-serif".
This should be done by CSS now, so if you're calling this, you shouldn't be.
=cut
sub curFont
{
return "Verdana, Arial, Helvetica, sans-serif";
}
=pod
=head1 TABLE GENERATION ROUTINES
=head2 genCell($q, $text)
=cut
sub genCell
{
my ($q, $text, $class) = @_;
if ($text =~ /^\s*$/){$text = "&nbsp;"}
if ($class) { return $q->td({-class => "$class"}, $text),"\n";}
else { return $q->td ($text),"\n";}
}
=pod
=head2 genDoubleCell($q, $text);
Generates a cell which spans two columns, containing the text specified.
=cut
sub genDoubleCell
{
my ($q, $text) = @_;
if ($text =~ /^\s*$/){ $text = "&nbsp;" }
return $q->td ({colspan => 2}, $text),"\n";
}
=pod
=head2 genSmallCell($q, $text, $type, $colspan)
Generates a cell with "small" text (font size is 80%).
"$type" can be one of:
"normal" : creates <td class="sme-border"> cell
"header" : creates <th class="sme-border"> cell
=cut
sub genSmallCell
{
my ($q, $text, $type, $colspan) = @_;
$text = '' unless defined $text;
$type ||= 'normal';
$colspan ||= 1;
if ($text =~ /^\s*$/){ $text = "&nbsp;" }
if ("$type" eq "header") {
return $q->th ({class=>"sme-border", colspan=>$colspan}, $text)."\n";
} else {
return $q->td ({class=>"sme-border", colspan=>$colspan}, $text)."\n";
}
}
=pod
=head2 genSmallCellCentered($q, $text)
Generates a cell with "small" text (font size is 80%), centered.
creates <td class="sme-border-center"> cell
=cut
sub genSmallCellCentered
{
my ($q, $text) = @_;
if ($text =~ /^\s*$/){ $text = "&nbsp;" }
return $q->td ({class => "sme-border-center"}, $text)."\n";
}
=pod
=head2 genSmallCellRightJustified($q, $text)
=head2 genSmallCellCentered($q, $text)
Generates a cell with "small" text (font size is 80%), right justified.
creates <td class="sme-border-right"> cell
=cut
sub genSmallCellRightJustified
{
my ($q, $text) = @_;
if ($text =~ /^\s*$/){ $text = "&nbsp;" }
return $q->td ({class => "sme-border-right"}, $text)."\n";
}
=pod
=head2 genSmallRedCell($q, $text)
Generates a cell with "small" text (font size is 80%), left justified.
creates <td class="sme-border-warning"> cell
=cut
sub genSmallRedCell
{
my ($q, $text) = @_;
if ($text =~ /^\s*$/){ $text = "&nbsp;" }
return $q->td ({class => "sme-border-warning"}, $text)."\n";
}
=pod
=head2 genTextRow($q, $text)
Returns a table row containing a two-column cell containing $text.
=cut
sub genTextRow
{
my ($q, $text) = @_;
if ($text =~ /^\s*$/){ $text = "&nbsp;" }
return "\n",$q->Tr ($q->td ({colspan => 2}, $text)),"\n";
}
=pod
=head2 genButtonRow($q, $button)
Returns a table row containing an empty first cell and a second cell
containing a button with the value $button.
=cut
sub genButtonRow
{
my ($q, $button) = @_;
# return $q->Tr ($q->td ({-class => "sme-submitbutton", -colspan => "2"},$q->b ($button))),"\n";
# return $q->Tr ($q->td ('&nbsp;'),
# $q->td ({-class => "sme-submitbutton"},$q->b ($button))),"\n";
return $q->Tr ({-class => "sme-layout"}, $q->th ({-class => "sme-layout", colspan => "2"},$q->b ($button))),"\n";
}
=pod
=head2 genNameValueRow($q, $fieldlabel, $fieldname, $fieldvalue)
Returns a table row with two cells. The first has the text
"$fieldlabel:" in it, and the second has a text field with the default
value $fieldvalue and the name $fieldname.
=cut
sub genNameValueRow
{
my ($q, $fieldlabel, $fieldname, $fieldvalue) = @_;
return $q->Tr (
$q->td ({-class => "sme-noborders-label"},
"$fieldlabel:"),"\n",
$q->td ({-class => "sme-noborders-content"},
$q->textfield (
-name => $fieldname,
-override => 1,
-default => $fieldvalue,
-size => 32))),"\n";
}
=pod
sub genWidgetRow($q, $fieldlabel, $popup)
=cut
# used only by backup panel as far as I can see
sub genWidgetRow
{
my ($q, $fieldlabel, $popup) = @_;
return $q->Tr ($q->td ("$fieldlabel:"),
$q->td ($popup));
}
=pod
=head1 STATUS AND ERROR REPORT GENERATION ROUTINES
=head2 genResult($q, $msg)
Generates a "status report" page, including the footer
=cut
sub genResult
{
my ($q, $msg) = @_;
print $q->p ($msg);
genFooter ($q);
}
=pod
=head2 genStateError($q, $confref)
Subroutine to generate "unknown state" error message.
=cut
sub genStateError
{
my ($q, $confref) = @_;
genHeaderNonCacheable ($q, $confref, "Internal error");
genResult ($q, "Internal error! Unknown state: " . $q->param ("state") . ".");
}
END
{
}
#------------------------------------------------------------
# return "1" to make the import process return success
#------------------------------------------------------------
1;
=pod
=head1 AUTHOR
Mitel Networks Corporation
For more information, see http://e-smith.org/
=cut

View File

@@ -0,0 +1,640 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::config;
use strict;
use vars qw($VERSION);
$VERSION = 1.45;
use Sys::Syslog qw(:DEFAULT setlogsock);
use Fcntl qw(:DEFAULT :flock);
use Carp qw(cluck);
my $Default_Config = '/home/e-smith/db/configuration';
=pod
=head1 NAME
esmith::config - Access e-smith config files via hashes
=head1 SYNOPSIS
use esmith::config;
my %config;
my $config_obj = tie %config, 'esmith::config', $config_file;
# Read in the value of Wibble from the $config_file.
print $config{Wibble};
# Write out the value of Wibble to the $config_file.
$config{Wibble} = 42;
my $filename = $config_obj->filename;
=head1 DESCRIPTION
The esmith::config package enables Perl programs to read and write
entries from the e-smith configuration file using a simple hash
interface.
The configuration file has a simple ASCII representation,
with one "key=value" entry per line.
=begin testing
use_ok('esmith::config');
chdir '10e-smith-lib';
%Expect = ( foo => 'bar',
'this key' => 'has whitespace',
'that key ' => 'has trailing whitespace',
' another key' => 'has leading whitespace',
'this value' => ' has leading whitespace',
'that value' => 'has trailing whitespace ',
'tricky value' => 'with=equals.',
);
=end testing
=head2 Tying
tie %config, 'esmith::config', $config_file;
Reads in the configuration from the given $config_file, returning a
tied hash (%config) populated with the keys & values from the
$config_file which you can then use like a normal hash. Any writes or
deletes are immediately written back to the $config_file.
If no $config_file is supplied it falls back to the environment variable
ESMITH_CONFIG_DB, and finally defaults to F</home/e-smith/db/configuration>
If the $config_file doesn't exist it will create one for you.
=begin testing
my %config;
tie %config, 'esmith::config', 'dummy.conf';
ok( tied %config, 'tie worked' );
is( $config{foo}, 'bar', ' theres stuff in it' );
ok( !exists $config{FILENAME}, ' it only contains config info');
is( tied(%config)->{FILENAME}, 'dummy.conf',
' and the real object is inside');
tie %config, 'esmith::config', 'I_dont_exist';
ok( tied %config, 'tying a non-existant file' );
is( keys %config, 0, ' and its empty' );
$config{foo} = 42;
isnt( -s 'I_dont_exist', 0 );
untie %config;
ok( unlink 'I_dont_exist' );
$ENV{ESMITH_CONFIG_DB} = "dummy.conf";
tie %config, 'esmith::config';
ok( tied %config, 'tie to ESMITH_CONFIG_DB worked' );
is_deeply(\%config, \%Expect, " picked up data");
=end testing
=head2 Methods
You can get at the underlying esmith::config object by using tied().
my $config_obj = tied %config;
it has a few useful methods.
=over 4
=item filename
my $file = $config_obj->filename;
Gets the config filename this object is tied to.
=begin testing
my %config;
my $obj = tie %config, 'esmith::config', 'dummy.conf';
is( $obj->filename, 'dummy.conf', 'filename()' );
=end testing
=cut
sub filename {
my($self) = shift;
return $self->{FILENAME};
}
=back
=begin _private
=head2 Private methods
=over 4
=item _readconf
my $config = _readconf($config_file);
Returns a hash ref of config key/value pairs read out of the given
$config_file. If $config_file doesn't exist an empty hash ref will be
returned.
_readconf() understands the config file to be formatted as individual
lines of simply:
key=value
any further complexity of parsing the value is handled elsewhere.
=end _private
=begin testing
my $config = esmith::config::_readconf('dummy.conf');
isnt( keys %$config, 0, '_readconf() got something' );
is_deeply( $config, \%Expect, ' read in the right values' );
$config = esmith::config::_readconf('I_dont_exist');
isa_ok( $config, 'HASH', '_readconf from a non-existent file' );
is( keys %$config, 0, ' and its empty' );
=end testing
=cut
sub _readconf
{
my ($self, $filename) = @_;
my %config = ();
unless (open (FH, $filename))
{
if (-f $filename)
{
&log("Config: ERROR: \"$filename\" exists but is not readable");
}
return \%config;
}
my $binmode = $self->_read_binmode;
binmode(FH, $binmode) if $binmode;
while (my $line = <FH>)
{
chomp $line;
# BLIND UNTAINT! Much code wrongly depends on this and
# they should be moved away from it.
$line =~ /(.*)/;
$line = $1;
# ignore comments and blank lines
next if $line =~ /^\s*$/ || $line =~ /^\s*#/;
my($key, $value) = split /=/, $line, 2;
$config{$key} = $value;
}
close(FH);
return \%config;
}
=begin _private
=item _writeconf
my $success = _writeconf($config_file, \%config);
The given $config_file is overwritten using the entries in %config.
Returns whether or not the write succeded.
=end _private
=begin testing
my $scratch = 'scratch.conf';
ok( esmith::config::_writeconf($scratch, \%Expect),
'_writeconf() says it worked' );
is_deeply( esmith::config::_readconf($scratch), \%Expect,
' wrote the right things' );
unlink $scratch;
=end testing
=cut
sub _writeconf
{
my ($self, $filename, $config) = @_;
eval {
if (-f $filename && ! -r $filename)
{
die "'$filename' exists but is not readable\n";
}
sysopen (FH, "$filename.$$", O_RDWR | O_CREAT, 0660)
or die "Cannot open $filename.$$: $!\n";
my $binmode = $self->_write_binmode;
binmode(FH, $binmode) if $binmode;
die "Error writing to $filename.$$: $!" unless
printf FH <<EOF, scalar localtime;
# DO NOT MODIFY THIS FILE.
# This file is automatically maintained by the Mitel Networks SME Server
# configuration software. Manually editing this file may put your
# system in an unknown state.
#
# updated: %s
EOF
foreach my $key (sort keys %$config)
{
print FH "$key=$config->{$key}\n"
or die "Error writing to $filename.$$: $!";
}
close (FH) or die "Error closing $filename.$$: $!";
rename("$filename.$$", $filename)
or die "Couldn't rename $filename.$$ to $filename: $!";
my $gid = getgrnam ('admin') || 0;
chown (0, $gid, $filename);
};
if($@) {
chomp $@;
&log($@);
&log("'$filename' will not be updated");
return;
}
else {
return 1;
}
}
=begin _private
=item B<_lock_write>
=item B<_lock_read>
$self->_lock_write;
$self->_lock_read;
Sets up read (shared) or write (exclusive) locks on the config file.
This is actually locking a semaphore file.
Returns if the lock succeeded or failed.
=item B<_unlock>
$self->_unlock
Unlocks the config file.
=end _private
=cut
sub _lock_write { $_[0]->_lock(LOCK_EX) }
sub _lock_read { $_[0]->_lock(LOCK_SH) }
sub _lock {
return if $] eq 5.006; # Locking is broken in perl 5.6.0
my($self, $lock) = @_;
my $semaphore = $self->{SEMAPHORE_FILE} = $self->{FILENAME}.'.lock';
eval {
open(my $fh, ">>$semaphore") or
die "Can't open '$semaphore' semaphore: $!";
$self->{SEMAPHORE} = $fh;
flock($fh, $lock) or
die "Can't lock '$semaphore' semaphore: $!";
};
if( $@ ) {
warn $@;
return;
}
else {
return 1;
}
}
sub _unlock {
return if $] eq 5.006; # Locking is broken in perl 5.6.0
my($self) = @_;
eval {
flock($self->{SEMAPHORE}, LOCK_UN) or
die "Can't unlock $self->{SEMAPHORE_FILE}: $!";
unlink $self->{SEMAPHORE_FILE};
delete $self->{SEMAPHORE_FILE};
delete $self->{SEMAPHORE};
};
if( $@ ) {
warn $@;
return;
}
else {
return 1;
}
}
=end _private
=back
=cut
#------------------------------------------------------------
# Constructor for the tied hash. If filename not specified,
# defaults to '/home/e-smith/db/configuration'.
#------------------------------------------------------------
sub TIEHASH
{
my $class = shift;
my $filename = shift || $ENV{ESMITH_CONFIG_DB} || $Default_Config;
if ($filename =~ m:^/home/e-smith/\w+$: )
{
cluck "*WARNING* esmith::config($filename) called with old " .
"database path. The following package needs to be updated: ";
$filename =~ s:e-smith:e-smith/db:;
}
my $self =
{
FILENAME => $filename,
CONFIG => {},
};
bless $self, $class;
$self->{CONFIG} = $self->_readconf($filename);
return $self;
}
#------------------------------------------------------------
# Look up a configuration parameter.
#------------------------------------------------------------
sub FETCH
{
my $self = shift;
my $key = shift;
# Trim leading and trailing whitespace from the key.
$key =~ s/^\s+|\s+$//g;
return $self->{CONFIG}{$key};
}
#------------------------------------------------------------
# Store a configuration parameter.
#------------------------------------------------------------
sub STORE
{
my $self = shift;
my $key = shift;
my $value = shift;
die "key not defined" unless defined $key;
die "value not defined for key $key" unless defined $value;
if( $value =~ /\n/ or $key =~ /\n/ ) {
&log("$self->{FILENAME}: esmith::config doesn't support newlines in ".
"keys or values. Truncating.");
$key =~ s/\n.*//s;
$value =~ s/\n.*//s;
}
# Trim leading and trailing whitespace from the key and value.
$key =~ s/^\s+|\s+$//g;
$value =~ s/^\s+|\s+$//g;
# Make sure that the value has a type. Given the format, it should be
# sufficient to ensure that it does not begin with a pipe char.
if ($value =~ /^\|/)
{
warn "ERROR: You should not set a config record without a type (key was $key).\n";
}
# read in config again, just in case it changed
$self->_lock_write;
$self->{CONFIG} = $self->_readconf($self->{FILENAME});
if (exists $self->{CONFIG}{$key} and
$self->{CONFIG}{$key} eq $value)
{
$self->_unlock;
return undef;
}
my $msg = "$self->{FILENAME}: OLD $key=";
if (exists $self->{CONFIG}{$key})
{
$msg .= "$self->{CONFIG}{$key}";
}
else
{
$msg .= "(undefined)";
}
&log($msg);
$self->{CONFIG} {$key} = $value;
&log("$self->{FILENAME}: NEW $key=$self->{CONFIG}{$key}");
$self->_writeconf ($self->{FILENAME}, $self->{CONFIG});
$self->_unlock;
return undef;
}
#------------------------------------------------------------
# Delete a configuration parameter.
#------------------------------------------------------------
sub DELETE
{
my $self = shift;
my $key = shift;
# Trim leading and trailing whitespace from the key.
$key =~ s/^\s+|\s+$//g;
# read in config again, just in case it changed
$self->_lock_write;
$self->{CONFIG} = $self->_readconf($self->{FILENAME});
my $previous = delete $self->{CONFIG} {$key};
$self->_writeconf ($self->{FILENAME}, $self->{CONFIG});
$self->_unlock;
&log("$self->{FILENAME}: DELETE $key=$previous");
return $previous;
}
=begin _private
=item CLEAR
tie method: Clear the configuration file
=end _private
=begin testing
my $scratch = 'scratch.conf';
tie %config, 'esmith::config', $scratch;
ok( tied %config, 'tying a non-existant file' );
is( keys %config, 0, ' and its empty' );
$config{fibble} = 'blah';
isnt( keys %config, 0, ' and its not empty now' );
%config = ();
is( keys %config, 0, ' and CLEAR made it empty again' );
unlink $scratch;
=end testing
=cut
sub CLEAR
{
my $self = shift;
$self->{CONFIG} = ();
$self->_writeconf ($self->{FILENAME}, $self->{CONFIG});
&log("$self->{FILENAME}: CLEAR");
return undef;
}
#------------------------------------------------------------
# Check whether a particular key exists in the configuration file.
#------------------------------------------------------------
sub EXISTS
{
my $self = shift;
my $key = shift;
# Trim leading and trailing whitespace from the key.
$key =~ s/^\s+|\s+$//g;
return exists $self->{CONFIG} {$key};
}
#------------------------------------------------------------
# FIRSTKEY is called whenever we start iterating over the
# configuration table. We cache the configuration table at
# this point to ensure reasonable results if the
# configuration file is changed by another program during
# the iteration.
#------------------------------------------------------------
sub FIRSTKEY
{
my $self = shift;
my $discard = keys %{$self->{CONFIG}}; # reset each() iterator
return each %{$self->{CONFIG}};
}
#------------------------------------------------------------
# NEXTKEY is called for all iterations after the first. We
# just keep returning results from the cached configuration
# table. A null array is returned at the end. If the caller
# starts a new iteration, the FIRSTKEY subroutine is called
# again, causing the cache to be reloaded.
#------------------------------------------------------------
sub NEXTKEY
{
my $self = shift;
return each %{$self->{CONFIG}};
}
#------------------------------------------------------------
# Log messages to syslog
#------------------------------------------------------------
sub log
{
# There is a bug in Perl 5.00504 and above. If you are using the unix
# domain socket, do NOT use ndelay as part of the second argument
# to openlog().
my $msg = shift;
$msg =~ s/[^[:ascii:]]/_/g;
my $program = $0;
setlogsock 'unix';
openlog($program, 'pid', 'local1');
syslog('info', "%s", $msg);
closelog();
}
=item _read_binmode
return undef, indicating that by default binmode() need not be called after
file open.
=end _private
=cut
sub _read_binmode
{
return undef;
}
sub _write_binmode
{
return undef;
}
=head1 BUGS and CAVEATS
You can't have newlines in keys or values.
While the config values happen to be untainted B<do not depend on this
behavior> as it will change in the future.
=head1 AUTHOR
SME Server Developers <bugs@e-smith.com>
For more information, see http://www.e-smith.org/
=head1 SEE ALSO
esmith::db
=cut
1;

View File

@@ -0,0 +1,25 @@
#----------------------------------------------------------------------
# Copyright 1999-2008 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::config::utf8;
use warnings;
use strict;
use vars qw(@ISA);
@ISA = qw(esmith::config);
sub _read_binmode
{
return ":encoding(UTF-8)";
}
sub _write_binmode
{
return ":utf8";
}
1;

View File

@@ -0,0 +1,603 @@
#----------------------------------------------------------------------
# Copyright 1999-2006 Mitel Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
=head1 NAME
esmith::console - A class to provide a backend library to the server console.
=head1 SYNOPSIS
use esmith::console;
my $console = esmith::console->new();
($rc, $choice) = $console->message_page
(
title => gettext("Administrator password not set"),
text => gettext("Sorry, you must set the administrator password."),
);
=head1 DESCRIPTION
This class provides a backend library of methods for the frontend console on
the server. The intent is that all of the whiptail code is hidden in this
library, and the frontend can just concern itself with the logical progression
through any and all applicable screens.
=head1 Methods
=cut
package esmith::console;
use strict;
use vars qw($VERSION @ISA @EXPORT_OK);
use esmith::util;
use Locale::gettext;
use esmith::ConfigDB;
use esmith::I18N;
@ISA = qw(Exporter);
use constant SCREEN_ROWS => 22;
use constant SCREEN_COLUMNS => 76;
use constant CONSOLE_SCREENS => "/sbin/e-smith/console-screens";
BEGIN
{
# disable CTRL-C
$SIG{INT} = 'IGNORE';
# Set PATH explicitly and clear related environment variables so that calls
# to external programs do not cause results to be tainted. See
# "perlsec" manual page for details.
$ENV {'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin';
$ENV {'SHELL'} = '/bin/bash';
delete $ENV {'ENV'};
delete $ENV {'BASH_ENV'};
}
=head2 new
This is the class constructor.
=cut
sub new
{
my $class = ref($_[0]) || $_[0];
my $self = {};
esmith::util::setRealToEffective ();
my $i18n = new esmith::I18N;
$i18n->setLocale("server-console");
#------------------------------------------------------------
# Set stdin, stdout and stderr to console
#------------------------------------------------------------
if (defined $ARGV [0])
{
$ARGV[0] =~ /(console|tty\d*)/ && -c "/dev/$1"
or die gettext("Bad ttyname:"), " ", $ARGV[0], "\n";
my $tty = $1;
open (STDIN, "</dev/$tty") or die gettext("Can't redirect stdin"), ": $!\n";
open (STDOUT, ">/dev/$tty") or die gettext("Can't redirect stdout"), ": $!\n";
my $pid = open(STDERR, "|-");
die gettext("Can't fork"), ": $!\n" unless defined $pid;
unless ($pid)
{
exec qw(/usr/bin/logger -p local1.info -t console);
}
}
$self = bless $self, $class;
return $self;
}
=head2 screen and dialog
These method are wrappers around whiptail and dialog, and permit the creation
of custom screens depending on the arguments passed. They are typically not
called directly, but are used by all of the other page methods that
follow. You should only call these method directly if none of the other
methods apply.
=cut
sub screen
{
_screen(shift, "/usr/bin/dialog", @_);
}
sub dialog
{
_screen(shift, "/usr/bin/dialog", @_);
}
sub whiptail
{
_screen(shift, "/usr/bin/whiptail", @_);
}
sub _screen
{
my $self = shift;
my $whiptail = shift;
my @whiptailArgs = @_;
# now would be a good time to flush output buffers, so the partial
# buffers don't get copied:
$| = 1;
print "";
pipe (READER, WRITER)
or die gettext("Couldn't create pipe") . ": $!\n";
my $pid = fork;
if (! defined $pid)
{
die gettext("Couldn't fork") . ": $!\n";
}
elsif ($pid == 0)
{
#----------------------------------------
# Child
#----------------------------------------
# Attach child's STDIN to the reading end of the pipe
close READER
or die gettext("Couldn't close reading end of pipe") , ": $!\n";
if ($whiptail =~ m{\bwhiptail$} ) {
# whiptail sends its output via STDERR. We temporarily
# shut off warnings so they don't interfere with that.
local $^W = 0;
open STDERR, ">& WRITER"
or die gettext("Couldn't connect STDERR to pipe"), ": $!\n";
close WRITER
or die gettext("Couldn't close writing end of pipe"), ": $!\n";
unshift @whiptailArgs, $whiptail,
'--backtitle', $self->backtitle;
} else {
use Fcntl qw/F_SETFD/;
# Clear close-on-exec on WRITER so that it stays open for dialog to use
fcntl(WRITER, F_SETFD, 0);
unshift @whiptailArgs, $whiptail,
'--backtitle', $self->backtitle, "--output-fd", fileno(WRITER);
}
exec @whiptailArgs;
die gettext("Couldn't exec:"), ": $!\n";
}
#----------------------------------------
# Parent
#----------------------------------------
close WRITER;
my $choice = <READER>;
close READER;
waitpid ($pid, 0);
my $rc = $?;
return ($rc, $choice);
}
=head2 backtitle
Console header line for each page
=cut
sub backtitle
{
my $self = shift;
my $db = esmith::ConfigDB->open_ro or die "Couldn't open ConfigDB\n";
sprintf("%-33s%45s",
($db->get_prop('sysconfig', 'ProductName') || "SME Server") . " " .
($db->get_prop('sysconfig', 'ReleaseVersion') || "UNKNOWN"),
"Copyright (C) 1999-2006 Mitel Corporation, Copyright (C) ____COPYYEARS____ Koozali Foundation, Inc."
);
}
=head2 message_page
This method should be used whenever a screen that displays a simple message
is required.
=cut
sub message_page
{
my $self = shift;
my %params = @_;
my $title = $params{title};
my $message_box = $params{text};
my $left = defined $params{left} ? $params{left} : gettext("Back");
my $right = defined $params{right} ? $params{right} : gettext("Next");
$self->screen ("--title", $title,
"--cancel-label", $left,
"--ok-label", $right,
"--clear",
"--msgbox", $message_box,
SCREEN_ROWS,
SCREEN_COLUMNS,
);
}
=head2 tryagain_page
This method displays a simple "try again" screen.
=cut
sub tryagain_page
{
my $self = shift;
my %params = @_;
my $title = $params{title};
my $choice = $params{choice};
my $try_again = "; " . gettext("please try again");
my $message_box = $title . ":'${choice}'" . $try_again;
$self->screen ("--title", $title,
"--cancel-label", gettext("Back"),
"--ok-label", gettext("Next"),
"--clear",
"--msgbox", $message_box,
SCREEN_ROWS,
SCREEN_COLUMNS,
);
}
=head2 password_page
This method displays a screen suitable for entering a password.
=cut
sub password_page
{
my $self = shift;
my %params = @_;
my $title = $params{title};
my $message_box = $params{text};
my $left = defined $params{left} ? $params{left} : gettext("Back");
my $right = defined $params{right} ? $params{right} : gettext("Next");
$self->dialog ("--title", $title,
"--insecure",
"--cancel-label", $left,
"--ok-label", $right,
"--clear",
"--passwordbox", "\n" . $message_box,
SCREEN_ROWS,
SCREEN_COLUMNS,
);
}
=head2 yesno_page
This method displays a simple yes/no screen, so the user can make a
simple binary selection.
=cut
sub yesno_page
{
my $self = shift;
my %params = @_;
my $title = $params{title};
my $text = $params{text};
my $left = defined $params{left} ? $params{left} : gettext("Yes");
my $right = defined $params{right} ? $params{right} : gettext("No");
my @args = (
"--title" => $title,
"--yes-label" => $left,
"--no-label" => $right,
);
push @args, "--defaultno" if defined $params{defaultno};
push @args, "--clear";
$self->screen (@args,
"--yesno", $text,
SCREEN_ROWS,
SCREEN_COLUMNS,
);
}
=head2 input_page
This method displays a simple input screen with an input box.
=cut
sub input_page
{
my $self = shift;
my %params = @_;
my $title = $params{title};
my $text = $params{text};
my $value = $params{value};
my $left = defined $params{left} ? $params{left} : gettext("Back");
my $right = defined $params{right} ? $params{right} : gettext("Next");
$self->screen("--title", $title,
"--cancel-label", $left,
"--ok-label", $right,
"--clear",
"--inputbox", $text,
SCREEN_ROWS,
SCREEN_COLUMNS,
$value
);
}
=head2 infobox
This method is similar to a messagebox, but exits immediately, without clearing the screen.
=cut
sub infobox
{
my $self = shift;
my %params = @_;
my $title = $params{title};
my $text = $params{text};
my $height = $params{height} || "8";
my $width = $params{width} || SCREEN_COLUMNS;
$self->screen("--title", $title,
"--infobox", $text,
$height,
$width,
);
}
=head2 textbox
A text box lets you display the contents of a text file in a dialog box.
It is like a simple text file viewer.
=cut
sub textbox
{
my $self = shift;
my %params = @_;
my $title = $params{title};
my $file = $params{file};
my $height = $params{height} || '20';
my $width = $params{width} || SCREEN_COLUMNS;
$self->screen("--title", $title,
"--textbox", $file,
$height,
$width,
);
}
=head2 menu_page
This method displays a screen with a menu.
=cut
sub menu_page
{
my $self = shift;
my %params = @_;
my $title = $params{title};
my $text = $params{text};
my @args = ("--clear", "--title", $title);
if ($params{default})
{
push @args, "--default-item", $params{default};
}
my $value = $params{value};
my $argsref = $params{argsref};
my $menu_rows = scalar @$argsref / 2;
$menu_rows = 10 if ($menu_rows > 10);
my $left = defined $params{left} ? $params{left} : gettext("Back");
my $right = defined $params{right} ? $params{right} : gettext("Next");
$self->dialog(@args,
"--cancel-label", $left,
"--ok-label", $right,
"--menu", $text,
SCREEN_ROWS,
SCREEN_COLUMNS,
$menu_rows,
@$argsref,
);
}
=head2 keep_option
??
=cut
sub keep_option
{
my $self = shift;
my ($value) = @_;
my $keep_phrase = gettext("Keep the current setting");
return ( gettext("keep"), "${keep_phrase}: $value" );
}
=head2 gauge
This method displays a progress bar. It takes a coderef as parameter, and uses
the coderef to drive the --gauge widget of the dialog program, as well as to
perform whatever actions are being reported by the progress bar. The coderef
should take one parameter, which is the file handle to write the controlling
text to. If the return value of the coderef is defined, it is displayed by a
message_page after the progress bar terminates.
All text used to update the progress bar should either be numbers between 0
and 100, or arbitrary text sandwiched between leading and training lines
of 'XXX' followed by newline. The numbers will update the percentage complete
of the display, and the text will update the displayed text. Updating the
displayed text will reset the precentage complete to 0, so text should always
be followed by number.
=cut
sub gauge
{
my $self = shift;
my $sub = shift;
my %params = @_;
my $title = $params{title} || 'Progress';
my $feedback_title = $params{feedback_title} || 'Status';
my $init_text = $params{text} || 'Progress';
my @args = (
'--backtitle', $self->backtitle,
'--title', gettext($title),
);
push @args, "--clear" if $params{clear};
push @args, "--colors" if $params{colors};
push @args, "--no-collapse" if $params{no_collapse};
use FileHandle;
unless (open(WR, '|-'))
{
exec('/usr/bin/dialog',
@args,
'--gauge',
gettext($init_text),
SCREEN_ROWS,
SCREEN_COLUMNS,
);
}
WR->autoflush(1);
my $text = &$sub(*WR);
close(WR);
$self->message_page('title' => $feedback_title, 'text' => $text)
if defined $text;
}
=head2 run_screens
This method takes a directory of screens to run, and runs them in order.
To support navigation between screens, this method respects an integer
return value from the screens.
0 = all is well, continue to the next screen
1 = all is not well, go back to the previous screen
2 = catastrophic failure - return from run_screen
=cut
sub run_screens
{
my $self = shift;
my ($subdir) = @_;
my $dir = CONSOLE_SCREENS . "/$subdir";
# This is fine. Noop if the directory isn't there.
unless (-d $dir)
{
return 1;
}
# This is not fine. If it's there, we should be able to open it.
unless ( opendir(SCREENS, $dir) )
{
warn "Failed to open directory $dir: $!\n";
return 0;
}
my @screens = sort grep (!/^(\.\.?)$/, readdir (SCREENS));
my @previous_screens = ();
while (@screens)
{
my $screen = shift @screens;
unless ( $screen =~ /(S\d\d[\d\w]+)/ )
{
warn "Unknown screen type $dir/$screen\n";
next;
}
$screen = $1;
my $rv = system( "$dir/$screen" );
$rv >>= 8;
if ($rv == 0)
{
# Success, move to next screen.
push @previous_screens, $screen;
}
elsif ($rv == 1)
{
# Failure, go back one screen.
unshift @screens, $screen;
if (@previous_screens)
{
unshift @screens, pop @previous_screens;
}
else
{
# We're at the beginning of the stack. Just return.
return 0;
}
}
else
{
# Catastrophic failure, return. While 2 is the agreed-upon
# return code for this, consider it a catastrophic failure
# if we don't get a valid return code.
return 0;
}
}
return 1;
}
=head1 AUTHOR
SME Server Developers <smebugs@mitel.com>
=cut
1;

View File

@@ -0,0 +1,743 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::db;
use esmith::config;
use strict;
=head1 NAME
esmith::db - Routines for handling the e-smith configuration database
=head1 SYNOPSIS
B<THIS MODULE HAS BEEN DEPRECATED>
use esmith::db;
use esmith::config;
my %config;
tie %config, 'esmith::config', $config_file;
db_set(\%config, $key, $type, \%properties);
db_set_type(\%config, $key, $type);
db_set_prop(\%config, $key, $property => $new_value);
my($type, %properties) = db_get(\%config, $key);
my $type = db_get_type(\%config, $key);
my %properties = db_get_prop(\%config, $key);
my $value = db_get_prop(\%config, $key, $property);
db_delete(\%config, $key);
db_delete_prop(\%config, $key, $property);
db_print(\%config, $key);
db_show(\%config, $key);
db_print_type(\%config, $key);
db_print_prop(\%config, $key, $prop);
=head1 DESCRIPTION
B<THIS MODULE HAS BEEN DEPRECATED>. Please use a subclass of
esmith::DB::db instead, such as esmith::AccountsDB or esmith::ConfigDB.
I<Do not try to change this module>. Much code depends on subtle
nuances and bugs and you will break things if you try to fix it.
Instead, move any existing code away from esmith::db and towards
esmith::DB::db.
This module provides utility routines for manipulating e-smith
configuration data. OO and non-OO versions of the routines are provided.
For example, db_set() is the non-OO while set() can be called with an
object reference.
E-Smith DB entries have three parts. A key, a type and a hash of
properties.
key squid
type cephalopod
properties arms => 10
species => Loligo
=cut
use vars qw($VERSION @ISA @EXPORT);
$VERSION = sprintf "%d.%03d", q$Revision: 1.16 $ =~ /(\d+)\.(\d+)/;
use Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(
db_set
db_get
db_delete
db_set_type
db_get_type
db_get_prop
db_set_prop
db_delete_prop
db_print
db_show
db_print_type
db_print_prop
);
=head2 Functions
=over 4
=item B<db_set>
my $success = db_set(\%config, $key, $raw_value);
my $success = db_set(\%config, $key, $type);
my $success = db_set(\%config, $key, $type, \%properties);
Enters a new $key into the %config or replaces an existing $key. It
sets the $type and optionally %properties.
As a "bug which has become a feature" you can feed db_set() the
$raw_value for a $key (ie. 'type|prop1|val1|prop2|val2') and it will
setup the types and properties properly. I<Do not depend on this> but
don't change it either. There's code that depends on this behavior.
It returns true on success, false on failure.
If the $key contains a newline it will fail.
=cut
sub db_set
{
my ($hash, $key, $new_value, $hashref) = @_;
return undef if ($key =~ /\n/);
if (defined $hashref)
{
my $properties = _db_hash_to_string($hashref);
if (defined $properties && $properties ne '')
{
$new_value .= "|$properties";
}
}
$new_value and $new_value =~ s/\n/\\n/g;
$$hash{$key} = $new_value;
return undef unless defined db_get($hash, $key);
return 1;
}
=head2 B<db_get>
my($type, %properties) = db_get(\%config, $key);
my $raw_value = db_get(\%config, $key);
my @keys = db_get(\%config);
Reads the $type and %properties for the given $key in %config.
In scalar context it returns the raw value of $config{$key} B<NOT> the
type! But it unescapes newlines. I<Use db_get_type() instead>.
If a $key is not given it returns all the @keys in the %config.
=cut
sub db_get
{
my ($hash, $key) = @_;
return sort keys %$hash unless defined $key;
return undef unless exists $$hash{$key};
my $value = $$hash{$key};
$value and $value =~ s/\\n/\n/g;
return wantarray() ? _db_string_to_type_and_hash($value) : $value;
}
=item B<db_delete>
db_delete(\%config, $key)
Deletes the $key from %config.
=cut
sub db_delete
{
my ($hash, $key) = @_;
return undef unless defined db_get($hash, $key);
delete $$hash{$key};
return 1;
}
=item B<db_set_type>
my $success = db_set_type(\%config, $key, $type)
Sets the $type for $config{$key}.
Returns true if the set succeeded, false otherwise.
=cut
sub db_set_type
{
my ($hash, $key, $type) = @_;
return undef unless defined db_get($hash, $key);
my %properties = db_get_prop($hash, $key);
return db_set($hash, $key, $type, \%properties);
}
=item B<db_get_type>
my $type = db_get_type(\%config, $key);
Returns the $type associated with the $key in the %config database.
Will return undef if the $key doesn't exist.
=cut
sub db_get_type
{
my ($hash, $key) = @_;
return undef unless defined db_get($hash, $key);
my ($type) =
_db_string_to_type_and_hash(db_get($hash, $key));
return $type;
}
=item B<db_set_prop>
my $success = db_set_prop(\%config, $key, $property => $new_value)
Sets the given $property of the $key in the %config database to the
$new_value. If the $property didn't exist, it will be added.
Returns true/value if it succeeded/failed.
=cut
sub db_set_prop
{
my ($hash, $key, $prop, $new_value) = @_;
return undef unless defined db_get($hash, $key);
my $type = db_get_type($hash, $key);
my %properties = db_get_prop($hash, $key);
$properties{$prop} = $new_value;
return db_set($hash, $key, $type, \%properties);
}
=item B<db_get_prop>
my %properties = db_get_prop(\%config, $key);
my $value = db_get_prop(\%config, $key, $property);
Returns the %properties for a $key in the %config database. If you
ask for a specific $property you'll get the $value for that $property.
Returns undef if the $key or $property doesn't exist.
=cut
sub db_get_prop
{
my ($hash, $key, $prop) = @_;
my $val = db_get($hash, $key);
return (defined $prop ? undef : ()) unless defined $val;
my($type, %properties) = _db_string_to_type_and_hash($val);
return %properties unless defined $prop;
return undef unless exists $properties{$prop};
return $properties{$prop};
}
=item B<db_delete_prop>
db_delete_prop(\%config, $key, $property)
Deletes a $property from the $key in the %config.
Returns undef if the $key doesn't exist.
=cut
sub db_delete_prop
{
my ($hash, $key, $prop) = @_;
return undef unless defined db_get($hash, $key);
my $type = db_get_type($hash, $key);
my %properties = db_get_prop($hash, $key);
delete $properties{$prop};
return db_set($hash, $key, $type, \%properties);
}
=back
=head2 Debugging Functions
These functions are useful for debugging.
=over 4
=item B<db_print>
db_print(\%config);
db_print(\%config, $key);
Prints out keys and raw values in the %config database. If $key is
given it prints the $key and its raw value. If no $key is given it
prints out all the keys and their raw values.
=cut
sub db_print
{
my ($hash, $key) = @_;
my @list;
if (defined $key)
{
return undef unless defined db_get($hash, $key);
@list = ($key);
}
else
{
@list = db_get($hash);
}
return undef unless scalar @list;
foreach (@list)
{
print "$_=", scalar db_get($hash, $_),"\n";
}
return 1;
}
=item B<db_show>
db_show(\%config);
db_show(\%config, $key);
Prints out keys and their values in a human readable format.
If $key is given it prints out the $key, type and properties of that
$key. Otherwise it prints out the key, type and properties for all
keys.
=cut
sub db_show
{
my ($hash, $key) = @_;
my @list;
if (defined $key)
{
return undef unless defined db_get($hash, $key);
@list = ($key);
}
else
{
@list = db_get($hash) unless defined $key;
}
return undef unless scalar @list;
foreach (@list)
{
print "$_=";
my $type = db_get_type($hash, $_);
if (defined $type)
{
print "$type\n";
}
else
{
print "\n";
next;
}
my %properties = db_get_prop($hash, $_);
next unless scalar keys %properties;
foreach my $property (sort keys %properties)
{
print " $property=$properties{$property}\n";
}
}
return 1;
}
=item B<db_print_type>
db_print_type(\%config);
db_print_type(\%config, $key);
Prints out keys and their types in the %config database.
If $key is given, it prints out just that $key and its type.
Otherwise it prints out all the keys and their types.
=cut
sub db_print_type
{
my ($hash, $key) = @_;
my @list;
if (defined $key)
{
return undef unless defined db_get($hash, $key);
@list = $key;
}
else
{
@list = db_get($hash);
}
return undef unless scalar @list;
foreach (@list)
{
print "$_=";
my $type = db_get_type($hash, $_);
print db_get_type($hash, $_),"\n" if defined $type;
print "\n" unless defined $type;
}
return 1;
}
=item B<db_print_prop>
db_print_prop(\%config, $key);
db_print_prop(\%config, $key, $property);
Prints out the properties (or a single $property) of the given $key in
the %config.
=cut
sub db_print_prop
{
my ($hash, $key, $prop) = @_;
my @list;
my %list;
return undef unless defined db_get($hash, $key);
if (defined $prop)
{
my $value = db_get_prop($hash, $key, $prop);
return undef unless defined $value;
%list = ($prop => $value);
}
else
{
%list = db_get_prop($hash, $key);
}
return undef unless scalar keys %list;
foreach (sort keys %list)
{
print "$_=$list{$_}\n";
}
return 1;
}
=head2 OO Interface
To add to the confusion, esmith::db has a vestigal object-oriented
interface. Use esmith::DB::db instead.
=over 4
=item B<new>
my $db = esmith::db->new($db_file)
Generates a new esmith::db object from the given $db_file
=cut
sub new
{
my($class,$dbname) = @_;
return $class->open($dbname);
}
=item B<open>
my $db = esmith::db->open($db_name);
my $db = esmith::db->open($db_file);
Takes a database name (or pathname) and opens the named database.
The database name form is preferred over the explicit pathname.
For example
$db->open( 'configuration' );
or
$db->open( '/path/to/configuration' );
=cut
sub open
{
my $self = shift;
my $dbName = shift;
my $class = ref($self) || $self;
my $dataFile = _db_path($dbName);
unless ( $esmith::db::REFCOUNT{$dataFile} )
{
warn "Reading $dataFile into cache\n" if ($esmith::db::DEBUG);
my %db;
tie %db, 'esmith::config', $dataFile;
$esmith::db::CACHE{$dataFile} = \%db;
}
$self = bless {
DBNAME => $dataFile,
CACHE => $esmith::db::CACHE{$dataFile}
}, $class;
$esmith::db::REFCOUNT{$dataFile}++;
return $self;
}
sub DESTROY
{
my $self = shift;
return $self->close();
}
=item B<close>
$db->close;
Closes this database.
=cut
sub close
{
my $self = shift;
my $dataFile = $self->{'DBNAME'};
$esmith::db::REFCOUNT{$dataFile}--;
if ( $esmith::db::REFCOUNT{$dataFile} == 0 )
{
delete $esmith::db::CACHE{$dataFile};
warn "esmith::db::close Closing $dataFile\n" if ($esmith::db::DEBUG);
}
elsif ( $esmith::db::REFCOUNT{$dataFile} > 0 )
{
warn "esmith::db::close Not closing $dataFile, references ",
$esmith::db::REFCOUNT{$dataFile}, "\n" if ($esmith::db::DEBUG);
}
else
{
$esmith::db::REFCOUNT{$dataFile} = 0;
warn "esmith::db::close Not closing $dataFile, zero references\n"
if ($esmith::db::DEBUG);
}
}
=item B<set>
=item B<set_type>
=item B<set_prop>
=item B<get>
=item B<get_type>
=item B<get_prop>
=item B<delete>
=item B<delete_prop>
These all work like their functional versions (ie. set() == db_set())
except it is not necessary to input the %config database.
=cut
sub AUTOLOAD
{
no strict 'refs';
# fully qualified sub-name stored in $AUTOLOAD package variable
$esmith::db::AUTOLOAD =~ /^(.*::)(.*)$/;
my ($pkg, $sub) = ($1, $2);
# use *foo{THING} syntax to check if sub is defined (see perlref)
if (defined *{"${pkg}db_${sub}"}{CODE})
{
my $self = shift;
my $cache = $esmith::db::CACHE{$self->{DBNAME}};
wantarray ? return (my @p = &{"${pkg}db_${sub}"}($cache, @_))
: return (my $p = &{"${pkg}db_${sub}"}($cache, @_));
}
}
=begin private
=head2 Private functions
=over4
=item B<_db_hash_to_string>
my $raw_value = _db_hash_to_string($hashref);
Takes a reference to a hash and returns a string of pipe "|" delimited
pairs suitable for being stored.
=cut
sub _db_hash_to_string
{
my ($hash) = @_;
my $string = '';
foreach (sort keys %$hash)
{
$string .= '|' if length($string);
$string .= "$_|";
$string .= $$hash{$_} if defined $$hash{$_};
}
return $string;
}
=pod
=item B<_db_string_to_type_and_hash>
my($type, %properties) = _db_string_to_type_and_hash($raw_value);
Takes the $raw_value, which is a | delimited string, and spits it up
into the $type (the first field) and its %properties (the rest).
Escaped pipes (\|) are properly ignored as a delimiter.
=cut
sub _db_string_to_type_and_hash ($)
{
my ($arg) = @_;
return ('', ()) unless defined $arg;
# The funky regex is to avoid escaped pipes.
# If you specify a negative limit empty trailing fields are omitted.
return split(/(?<!\\)\|/, $arg, -1);
}
=item B<_db_path>
my $dfile = _db_path($database_name);
Takes a $database_name and returns the $file where it lives.
=cut
sub _db_path($)
{
my ($file) = @_;
if ($file =~ m:^/:)
{
return $file;
}
return "/home/e-smith/db/$file" if (-e "/home/e-smith/db/$file");
if (-e "/home/e-smith/$file")
{
warn "Database found in old location /home/e-smith/$file";
return "/home/e-smith/$file";
}
else
{
return "/home/e-smith/db/$file";
}
}
=back
=end private
=head1 BUGS and CAVEATS
keys cannot contain newlines or pipes.
types and properties cannot contain pipes.
=head1 AUTHOR
Mitel Networks Corporation
For more information, see http://www.e-smith.org/
=cut
1;

View File

@@ -0,0 +1,133 @@
#----------------------------------------------------------------------
# Copyright 1999-2005 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::ethernet;
#----------------------------------------------------------------------
use strict;
use File::Basename;
=head1 NAME
esmith::ethernet - Ethernet-related utility routines for e-smith
=head1 VERSION
This file documents C<esmith::ethernet> version B<1.4.0>
=head1 SYNOPSIS
use esmith::ethernet;
=head1 DESCRIPTION
This module contains routines for
=pod
=head2 probeAdapters()
Probe for any recognised adapters
=cut
sub probeAdapters ()
{
opendir(my $dh, "/sys/class/net") or die "Couldn't open /sys/class/net: $!";
my @nics = grep { $_ !~ m/^\./ } readdir($dh);
closedir($dh);
my $adapters = '';
my $index = 1;
foreach my $nic (@nics){
# Untaint $nic and makes sure the name looks OK
next unless ($nic =~ m/^(\w+[\.:]?\d+)$/);
$nic = $1;
next if (
# skip loopback
$nic eq 'lo' ||
# skip non links
!-l "/sys/class/net/$nic" ||
# skip wireless nics
-d "/sys/class/net/$nic/wireless" ||
-l "/sys/class/net/$nic/phy80211" ||
# skip bridges
-d "/sys/class/net/$nic/bridge" ||
# skip vlans
-f "/proc/net/vlan/$nic" ||
# skip bonds
-d "/sys/class/net/$nic/bonding" ||
# skip tun/tap
-f "/sys/class/net/$nic/tun_flags" ||
# skip dummy
-d "/sys/devices/virtual/net/$nic"
);
# Now we should be left only wth ethernet adapters
open HW, "/sys/class/net/$nic/address";
my $mac = join("", <HW>);
close HW;
# Check MAC Addr and untaint it
next unless ($mac =~ m/^(([\da-f]{2}:){5}[\da-f]{2})$/i);
$mac = $1;
# If the device is a slave of a bridge, it's real MAC
# address can be found in /proc/net/bonding/bondX
if (-l "/sys/class/net/$nic/master"){
my $bond = basename (readlink "/sys/class/net/$nic/master");
local $/ = '';
open SLAVES, "/proc/net/bonding/$bond";
my @slaves = <SLAVES>;
close SLAVES;
my @slaveInfo = grep { /^Slave\ Interface:\ $nic/m } @slaves;
foreach (split /\n+/, (join "", @slaveInfo)){
$mac = $1 if (/^Permanent\ HW\ addr:\ (.*)$/);
}
}
chomp($mac);
my $driver = basename (readlink "/sys/class/net/$nic/device/driver");
# Untaint driver name
next unless ($driver =~ m/^([\w\-]+)$/);
$driver = $1;
my $bus = basename (readlink "/sys/class/net/$nic/device/subsystem");
my $desc = $nic;
if ($bus eq 'pci'){
my $dev = basename (readlink "/sys/class/net/$nic/device");
# Untaint $dev
if ($dev =~ m/^(\d+:\d+:\d+\.\d+)$/){
$dev = $1;
$desc = `/sbin/lspci -s $dev`;
# Extract only description
$desc =~ m/^.*:.*:\s+(.*)\s*/;
$desc = $1;
}
}
elsif ($bus eq 'virtio'){
$desc = 'Virtio Network Device';
}
# TODO: we should also try to get the description of USB devices
$adapters .= "EthernetDriver" . $index++ . "\t" . $driver . "\t" .
$mac . "\t" . "\"$desc\"" . "\t" . $nic ."\n";
}
return $adapters;
}
#----------------------------------------------------------------------
# Return one to make the import process return success.
#----------------------------------------------------------------------
1;
=pod
=AUTHOR
SME Server Developers <bugs@e-smith.com>
For more information see http://www.e-smith.org/
=cut

View File

@@ -0,0 +1,220 @@
#----------------------------------------------------------------------
# Copyright 1999-2005 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::event;
use strict;
use Exporter;
use Time::HiRes qw( usleep ualarm gettimeofday tv_interval );
use esmith::Logger;
use File::Basename;
use File::Temp qw/ :mktemp /;
use esmith::ConfigDB;
use POSIX;
=pod
=head1 NAME
esmith::event - Routines for handling e-smith events
=head1 SYNOPSIS
use esmith::event;
my $exitcode = event_signal($event, @args);
=head1 DESCRIPTION
=cut
our $VERSION = sprintf '%d.%03d', q$Revision: 1.16 $ =~ /: (\d+).(\d+)/;
our @ISA = qw(Exporter);
our @EXPORT = qw(event_signal);
our @EXPORT_OK = ();
our %EXPORT_TAGS = ();
our $return_value = undef;
tie *LOG, 'esmith::Logger', 'esmith::event';
sub event_signal
{
my ($event, @args) = @_;
if ($event eq "actions")
{
warn("'actions' is not a valid event name.\n");
return;
}
my $events = "/etc/e-smith/events";
my $handlerDir = "$events/$event";
#------------------------------------------------------------
# get event handler filenames
#------------------------------------------------------------
opendir (DIR, $handlerDir)
|| die "Can't open directory $handlerDir\n";
# Create a hash of handlers (ignore directories),
# with value of basename(handler)
my %handlers = ();
foreach (grep {! -d "$handlerDir/$_"} readdir (DIR))
{
$handlers{"$handlerDir/$_"} = $_;
}
closedir (DIR);
# Add generic handlers to list, if their metadata directories
# exist
$handlers{"$events/actions/generic_template_expand"} = "S05generic_template_expand"
if ( -d "$handlerDir/templates2expand");
$handlers{"$events/actions/adjust-services"} = "S90adjust-services"
if ( -d "$handlerDir/services2adjust");
#------------------------------------------------------------
# Execute all handlers, sending any output to the system log.
#
# Event handlers are not supposed to generate error messages
# under normal conditions, so we do not provide a mechanism
# for event handlers to signal errors to the user. Errors can
# only be written to the log file.
#------------------------------------------------------------
print LOG "Processing event: $event @args";
#------------------------------------------------------------
# Run handlers, logging all output.
#------------------------------------------------------------
# assume success
my $exitcode = 1;
foreach my $filename
(sort { $handlers{$a} cmp $handlers{$b} } keys %handlers)
{
my $handler = basename $filename;
my $startTime = [gettimeofday];
my $status = -1;
if (-x $filename)
{
print LOG "Running event handler: $filename";
unless (($status = _mysystem(\*LOG, $filename, $event, @args)) == 0)
{
# if any handler fails, the entire event fails
$exitcode = 0;
}
}
else
{
print LOG "Skipping non-executable event handler: $filename";
next;
}
my $endTime = [gettimeofday];
my $elapsedTime = tv_interval($startTime, $endTime);
my $log = "$handler=action|Event|$event|Action|$handler";
$log .= "|Start|@$startTime|End|@$endTime|Elapsed|$elapsedTime";
$log .= "|Status|$status" if $status;
print LOG $log;
}
# Implement event queuing for clustered systems.
my $qfifo = "/var/spool/eventq";
return $exitcode unless (-e $qfifo);
# Ensure we aren't called by a cascaded event. We only need to
# queue the top-level of such a beast.
my $ppid = getppid();
open F, "/proc/$ppid/cmdline";
my $cmd = <F>;
close F;
unless($cmd =~ "/etc/e-smith/event")
{
my $fd = POSIX::open($qfifo, &POSIX::O_WRONLY) or return $exitcode;
my $argstr = join(" ",$event,@args);
$argstr .= "\n";
POSIX::write($fd, $argstr, length($argstr));
POSIX::close($fd);
}
return $exitcode;
}
sub _mysystem
{
my ($logger, $filename, $event, @args) = @_;
my $pid = open(PIPE, "-|");
die "Failed to fork: $!\n" unless defined $pid;
if ($pid)
{
# Parent
while (my $line = <PIPE>)
{
print $logger $line;
}
}
else
{
# Child
open(STDERR, ">&STDOUT");
exec($filename, $event, @args);
}
close(PIPE);
return $?;
}
#------------------------------------------------------------
# Attempt to eval perl handlers for efficiency - not currently used
# return 1 on success; 0 on error
#------------------------------------------------------------
sub _runHandler($)
{
my ($filename) = @_;
open(FILE, $filename) || die "Couldn't open $filename: $!";
my @lines = <FILE>;
close FILE;
my $string = "";
unless ( $lines[0] =~ /^#!.*perl/ )
{
# STDOUT and STDERR are both redirected going to LOG
return (system($filename, @ARGV) == 0) ? 1 : 0;
}
map { $string .= $_ } @lines;
print "Eval of $filename...";
# Override 'exit' in symbol table for handlers
sub exit { die "$_[0]\n" };
*CORE::GLOBAL::exit = \&esmith::event::exit;
my $status = eval $string;
chomp $@;
# if $@ is defined, then die or exit was called - use that status
$status = $@ if defined $@;
# for all exit values except 0, assume failure
if ($@)
{
print "Eval of $filename failed: $status\n";
return 0;
}
print "$status\n";
return 1;
}
1;

View File

@@ -0,0 +1,61 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::lockfile;
use strict;
use vars qw($VERSION @ISA @EXPORT_OK);
use Exporter;
use Fcntl ":flock";
use FileHandle;
@ISA = qw(Exporter);
@EXPORT_OK = qw(
LockFileOrReturn LockFileOrWait UnlockFile
);
sub LockFileOrReturn ($)
{
# Attempt to lock a file. If the lock fails, return immediately.
my $lock_file = shift;
my $FH = new FileHandle;
$FH->open(">> $lock_file")
or die "Cannot open lock file $lock_file for writing: $!.\n";
flock($FH, LOCK_EX | LOCK_NB) or return 0;
return $FH;
}
sub LockFileOrWait ($)
{
# Attempt to lock a file. Wait until the file is available.
my $lock_file = shift;
my $FH = new FileHandle;
$FH->open(">> $lock_file")
or die "Cannot open lock file $lock_file for writing: $!.\n";
flock($FH, LOCK_EX) or return 0;
return $FH;
}
sub UnlockFile ($)
{
my $FH = shift;
flock($FH, LOCK_UN);
$FH->close;
}
1;

View File

@@ -0,0 +1,76 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::event;
use strict;
=pod
=head1 NAME
esmith::logrotate - Routines for handling rotation of log files
=head1 SYNOPSIS
use esmith::logrotate;
NewLogFileSymlink($file);
MakeFilenameFromSymlink($file);
=head1 DESCRIPTION
=cut
BEGIN
{
}
sub NewLogFileSymlink
{
my $file = shift;
unless (defined $file)
{
warn("newlogfilesymlink called with no argument");
return;
}
my $time = time();
if (-f "/var/log/${file}")
{
my ($sec,$min,$hour,$mday,$mon,$year) = localtime($time - 1);
my $target = sprintf("%s%04d%02d%02d%02d%02d%02d",
$file, $year+1900, $mon, $mday, $hour, $min, $sec);
move("/var/log/${file}", "/var/log/${target}") or
die "Could not move /var/log/${file} to /var/log/${target}";
}
my ($sec,$min,$hour,$mday,$mon,$year) = localtime($time);
my $target = sprintf("%s%04d%02d%02d%02d%02d%02d",
$file, $year+1900, $mon, $mday, $hour, $min, $sec);
unlink("/var/log/${file}") or
warn "Could not unlink /var/log/${file}";
symlink("/var/log/${target}", "/var/log/${file}") or
warn "Could not symlink /var/log/${target} to /var/log/${file}";
}
sub MakeFilenameFromSymlink
{
use File::Basename;
my $filename = shift;
return $filename unless (-l $filename);
my $link = readlink $filename;
my $directory = dirname($filename);
return "${directory}/${link}";
}
END
{
}
1;

View File

@@ -0,0 +1,140 @@
#----------------------------------------------------------------------
# Copyright 2005-2006 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::tcpsvd;
use strict;
=pod
=head1 NAME
esmith::tcpsvd - Manage tcpsvd "peers" directory
=head1 SYNOPSIS
use esmith::tcpsvd;
esmith::tcpsvd::configure_peers($service)
=head1 DESCRIPTION
This module provides utility functions for use with tcpsvd from Gerrit
Pape's ipsvd package - see http://smarden.org/ipsvd/.
=cut
use esmith::NetworksDB;
use esmith::ConfigDB;
use esmith::util;
use esmith::lockfile;
use Carp;
=head2 configure_peers($service [, $peers_directory] )
configure_peers() configures the "peers" direectory used by tcpsvd for
access control and environment maipulation. $service identifies the name
of the service managed by supervise or runit. The peers directory (as
specified by the optional $peers_directory argument, defaulting to
"/var/service/$service/peers") is expected to contain files "0" defining
access conditions for public (default) accesss, and "local", defining
access conditions for local access. configure_peers() creates a set of
symlinks so that tcpsvd uses "local" for all local network access to
the service.
See http://smarden.org/ipsvd/ipsvd-instruct.5.html for all details of
the contents of the peers directory.
=cut
sub configure_peers
{
my $service = shift;
my $peers = shift || "/var/service/$service/peers";
unless (opendir(PEERS, $peers))
{
carp "Cannot read peers directory: $!";
return;
}
my $config = esmith::ConfigDB->open;
unless ($config)
{
carp "Could not open config db.";
return;
}
$service = $config->get($service);
unless ($service)
{
carp "No service record for $service";
return;
}
my $access = $service->prop('access') || "localhost";
my $nets = esmith::NetworksDB->open;
unless ($nets)
{
carp "Could not open networks db.";
return;
}
my $gw = $config->get('GatewayIP');
# Make a list of local networks, in prefix format
my %nets = ($access eq "localhost") ? () :
map
{
$_ => 1,
}
map
{
esmith::util::computeAllLocalNetworkPrefixes($_->key, $_->prop('Mask'));
}
($nets->get_all_by_prop('type', 'network'));
$nets{'127.0.0.1'} = 1;
# Setup lock on peers directory while we manipulate it
my $lock = esmith::lockfile::LockFileOrWait("$peers/local");
# Now manage a set of symlinks to the "local" instructions file
foreach my $insfile (readdir (PEERS))
{
next unless -l "$peers/$insfile";
if (exists $nets{$insfile})
{
# Cross this one off the list so that we don't bother creating it
delete $nets{$insfile};
}
else
{
# We no longer need this entry
unlink "$peers/$insfile" or
warn "Could not delete access control file $peers/$insfile: $!\n";
}
}
closedir(PEERS);
foreach my $insfile (keys %nets)
{
symlink "local", "$peers/$insfile" or
warn "Cannot add instructions file for $peers/$insfile: $!\n";
}
if (defined $gw)
{
# We have a defined gateway address - make sure that the router doesn't have
# relay privileges
my $gw_ip = $gw->value;
unlink "$peers/$gw_ip";
symlink "0", "$peers/$gw_ip" or
warn "Cannot add instructions file for $peers/$gw_ip: $!\n";
}
# Unlock peers directory
$lock && esmith::lockfile::UnlockFile($lock);
}
1;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,201 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::util::link;
use strict;
use esmith::ConfigDB;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(getExternalLink);
=head1 NAME
esmith::util::link - utilities for manipulating network links
=head1 SYNOPSIS
use esmith::util::link qw(getExternalLink);
# wait at most 60 seconds for the link to come up
my $timeout = 60;
# now just get the link
if (getExternalLink($timeout))
{
# the link is up
}
else
{
# the link didn't come up
}
=head1 DESCRIPTION
This is a collection of generally useful functions for manipulating network
links.
Functions are exported only on request.
=head2 Functions
=over 4
=item I<getExternalLink($timeout)>
Bring up the external link if it is not already up, waiting at most $timeout
seconds. If a $timeout is not specified, it defaults to 300 (5 minutes) for
dialup connections. This function can be used for both dialup and dedicated
connections, but dedicated connections will always return 1 (true).
Returns 1 if the external link is already up, or if it comes up within the
specfied $timeout period.
Returns 0 if the external link does not come up within the specified $timeout
period.
=cut
sub getExternalLink
{
my $timeout = shift;
my $configdb = esmith::ConfigDB->open;
my $rec = $configdb->get("AccessType");
my $accessType = $rec->value;
if ($accessType eq "dialup")
{
return _getDialupLink($timeout);
}
elsif ($accessType eq "dedicated")
{
# assume we are up
return 1;
}
else
{
# unknown access type
return 0;
}
}
=begin _private
=item I<getDialupLink($timeout)>
Bring up the ppp0 link, waiting at most $timeout seconds.
Returns 1 if the link comes up within the timeout period.
Returns 0 if the link does not come up within the timeout period.
The default timeout is 300 seconds.
=end _private
=cut
sub _getDialupLink
{
local $|=1;
my $timeout = shift(@_) || 300;
# check for existing ppp link
if (-f "/var/run/ppp0.pid")
{
# already up - return 1
return 1;
}
# create a diald monitor channel
my $ctlFile = "/etc/diald/diald.ctl";
my $monFile = "/tmp/diald.monitor.$$";
system('/bin/mknod', $monFile, 'p') == 0
or die "Can't mknod $monFile: $!\n";
# open control channel to diald
open (CTL, ">$ctlFile") or die "Can't open $ctlFile: $!\n";
# set up a child process to monitor the channel
my $pid = fork;
die "Can't fork: $!" unless defined $pid;
if ($pid)
{
# parent
# if the pipe reader isn't up first, diald will bail, so we open
# another pipe writer just to wait for the pipe reader
open (MON_W, ">$monFile") or die "can't open $monFile: $!\n";
# begin monitoring diald status via monitor fifo
print CTL "monitor $monFile\n";
close CTL;
# ok, everything is up and ready - send USR1 to diald
open (PID, "</var/run/diald.pid")
or die "can't open diald pidfile: $!\n";
my $dialdPid = <PID>;
close PID;
kill 'USR1', $dialdPid;
# Wait for the child to exit, then check for link again
waitpid($pid, 0);
close MON_W;
}
else
{
# child
open (MON, "<$monFile") or die "Can't open $monFile: $!\n";
# Parse the diald monitor stream for state information
my $state = "";
my $elapsed = 0;
while (<MON>)
{
# lucky us; diald sends a STATUS msg every second
if (/^STATUS/)
{
$elapsed++;
if ($elapsed >= $timeout)
{
# time is up - exit with failure code
exit 1;
}
}
elsif (/^STATE/)
{
$state = $_;
}
elsif ($state eq 'UP')
{
# the link is up - exit with success code
exit 0;
}
next;
}
close MON;
# end child
}
# parent (cont)
unlink $monFile;
if ($? == 0 || -f "/var/run/ppp0.pid")
{
# ok we're up - return 1 (true)
return 1;
}
else
{
# out of time - return 0 (false)
return 0;
}
}
1;

View File

@@ -0,0 +1,312 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::util::network;
use strict;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(isValidIP cleanIP isValidPort cmpIP);
our %EXPORT_TAGS = (
all => [qw(isValidIP cleanIP isValidPort cmpIP)]
);
use Net::IPv4Addr qw(:all);
use Mail::RFC822::Address;
use esmith::AccountsDB;
use constant TRUE => 1;
use constant FALSE => 0;
=for testing
use_ok('esmith::util::network');
=head1 NAME
esmith::util::network - miscellaneous network utilities
=head1 SYNOPSIS
use esmith::util::network qw(the functions you want);
my $ip = cleanIP($orig_ip);
my $is_valid = isValidIP($ip);
my $is_valid = isValidPort($port);
=head1 DESCRIPTION
This is a collection of generally useful functions for working with IP
addresses.
Functions are exported only on request.
=head2 Functions
=over 4
=item I<cleanIP>
my $ip = cleanIP($orig_ip);
If the $orig_ip is valid it will be cleaned up into a cannonical form,
stripping any padding zeros and such.
=begin testing
use esmith::util::network qw(cleanIP);
my %ips = (
'000.000.000.000' => '0.0.0.0',
'0.0.0.0' => '0.0.0.0',
'001.2.003.4' => '1.2.3.4',
'100.2.3.4' => '100.2.3.4',
'10.13.14.015' => '10.13.14.15',
'10.33.15.109' => '10.33.15.109',
'1.2.3.4.5' => ''
);
while( my($ip, $cleanip) = each %ips ) {
is( cleanIP($ip), $cleanip, "cleanIP($ip)" );
}
=end testing
=cut
sub cleanIP {
my $ip = shift;
return '' unless isValidIP($ip);
$ip =~ s/\b0+(\d+)/$1/g;
return isValidIP($ip) ? $ip : '';
}
=item I<isValidIP>
my $is_valid = isValidIP($ip);
Returns the IP given if $ip is a properly formatted IP address, undef otherwise.
=begin testing
use esmith::util::network qw(isValidIP);
my @goodIPs = qw(1.2.3.4
0.0.0.0
255.255.255.255
001.002.003.004
1.32.123.213
192.168.0.3
02.19.090.19
);
foreach my $ip (@goodIPs) {
ok( isValidIP($ip), "valid $ip");
}
my @badIPs = qw(256.3.2.4
-1.39.9.23
0
1
255.255.255.255.0
239..19.23.12
1.2.3.4.
foo.bar.com
);
foreach my $ip (@badIPs) {
ok( !isValidIP($ip), "invalid $ip");
}
=end testing
=cut
sub isValidIP($)
{
my ($string) = @_;
return unless defined ipv4_chkip($string);
return $string eq ipv4_chkip($string);
}
=item I<isValidPort>
my $is_valid = isValidPort($port);
Returns true if $port is a properly formatted port, false otherwise.
=begin testing
@badports = (98765434, -183, 0, 'bad port', 'a');
@goodports = (67, 23, 1, 54736);
foreach $port (@badports) {
isnt(esmith::util::network::isValidPort($port), 1);
}
foreach $port (@goodports) {
is(esmith::util::network::isValidPort($port), 1);
}
=end testing
=cut
sub isValidPort($)
{
my $port = shift;
return FALSE unless defined $port;
if (($port =~ /^\d+$/) &&
($port > 0) &&
($port < 65536))
{
return TRUE;
}
else {
return FALSE;
}
}
=item I<cmpIP>
Performs a cmp operation on two IP addresses.
=begin testing
$ip1 = '24.123.212.87';
$ip2 = '240.34.216.12';
is(esmith::util::network::cmpIP($ip1, $ip2), -1);
is(esmith::util::network::cmpIP($ip2, $ip1), 1);
is(esmith::util::network::cmpIP($ip1, $ip1), 0);
=end testing
=cut
sub cmpIP($$)
{
my $ip1 = ipv4_chkip(shift);
my $ip2 = ipv4_chkip(shift);
die "The first argument is not a valid IP address.\n" if not $ip1;
die "The second argument is not a valid IP address.\n" if not $ip2;
my @ip1cmps = split /\./, $ip1;
my @ip2cmps = split /\./, $ip2;
while (@ip1cmps)
{
my $cmp1 = shift @ip1cmps;
my $cmp2 = shift @ip2cmps;
my $cmp = $cmp1 <=> $cmp2;
return $cmp if $cmp;
}
return 0;
}
=item I<isValidHostname>
This function returns true if it is passed a valid RFC 921 hostname,
false otherwise.
=cut
sub isValidHostname
{
my $host_or_ip = shift;
unless (isValidIP($host_or_ip))
{
# It's not an IP address. Does it look like a hostname?
# FIXME: We could do a DNS lookup to be sure.
# (See RFC 921, "Domain Name System Implementation Schedule,"
# FIXME: Put this in a library.
unless ($host_or_ip =~ m{
# Must begin with an alphabetical character...
^[a-z]
# optionally followed by zero or more alphabetic characters,
# hyphens, periods and numbers...
[-a-z.0-9]*
(
# followed by one period...
\.
# and a repeat of the first pattern
[a-z]
[-a-z.0-9]*
)+
# which we can repeat one or more times, to the end of the
# string.
$
# Case insensitive.
}ix)
{
return 0;
}
}
return 1;
}
=item I<isValidEmail>
This validation function validates an email address, using the
Mail::RFC822::Address module. Additionally, by default, it permits a local
address instead of a fully-qualified remote address, even checking the
existence of said user in the accounts db.
If you don't wish to permit local addresses, pass the permitlocal option as
false.
ie. esmith::util::isValidEmail($address, { permitlocal => 0 })
=cut
sub isValidEmail
{
my $address = shift;
my $hashref = shift || {};
my %defaults = ( permitlocal => 1 );
my %options = (%defaults, %$hashref);
if (Mail::RFC822::Address::valid($address))
{
return TRUE;
}
# Permit a local address.
if ($address =~ /^[a-zA-Z][a-zA-Z0-9\._\-]*$/)
{
# Exception for 'admin' user. FIXME - I'd rather not hardcode this,
# but we can't permit email to all system users.
return TRUE if $address eq 'admin';
# Make sure the user exists.
my $accountsdb = esmith::AccountsDB->open_ro;
my $user = $accountsdb->get($address) || '';
unless (($user) && ($user->prop('type') eq 'user'))
{
return FALSE;
}
else
{
return TRUE;
}
}
return FALSE;
}
=back
=head1 AUTHOR
Mitel Networks Corp.
=cut
1;

View File

@@ -0,0 +1,215 @@
#----------------------------------------------------------------------
# Copyright 1999-2003 Mitel Networks Corporation
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#----------------------------------------------------------------------
package esmith::util::system;
use strict;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(killall rsync rsync_ssh);
=for testing
use_ok('esmith::util::system', 'killall', 'rsync', 'rsync_ssh');
=head1 NAME
esmith::util::system - replacements/wrappers for system() commands
=head1 SYNOPSIS
use esmith::util::system qw(killall rsync rsync_ssh);
killall($signal, @commands);
rsync($src, $dest, @options);
rsync_ssh($src, $dest, $user, $ident, \@rsync_opts, \@ssh_opts);
=head1 DESCRIPTION
This is for common functions that would normally require a system(...)
command. Makes things easier to document, test and upgrade.
If you find yourself writing a system() command, consider putting it
in here.
=head2 Functions
These may be pure Perl functions or they may well just be wrappers
around system() commands.
Each can be imported on request.
=over 4
=item B<killall>
my $killed_something = killall($signal, @commands);
Sends a $signal to all of the named @commands. $signal can be
specified by name or number (so 1 or HUP for example, names are
prefered).
Returns true if something was killed, false otherwise.
=begin testing
open(SCRATCH, ">scratch.exe") || die $!;
# XXX Irritating perl bug ends POD processing if it sees /^#!.*perl/
print SCRATCH sprintf <<'ENDING', '/usr/bin/perl';
#!%s -w
sleep 99;
ENDING
close SCRATCH;
END { unlink 'scratch.exe', 'scratch.out' }
chmod 0755, 'scratch.exe' || die $!;
my $pid = open(SCRATCH, "./scratch.exe |");
ok( $pid, 'scratch program started ok' );
ok( killall('USR1', 'scratch.exe'), 'killall returned properly' );
close SCRATCH; # so scratch.exe responds to the signal and exits
is( kill(9, $pid), 0, 'killall worked' );
# I can't actually think of a way to explicitly check this but it
# will make noise if it doesn't work.
ok( !killall('USR1', 'I_dont_exist_nope'),
'returned properly for killing nothing' );
ok( 1, 'killall is quiet when nothing is killed' );
=end testing
=cut
sub killall {
my($signal, @commands) = @_;
warn "You don't need a - on the signal" if $signal =~ /^-/;
my $killed_something =
system('/usr/bin/killall', '-q', "-$signal", @commands);
return !$killed_something;
}
=back
=item B<rsync>
my $success = rsync($source, $destination, @options);
rsyncs the $source file or directory to the $destination. Any
@options are direct options to the rsync command.
rsync will be run --quiet by default.
Returns true if the rsync succeeds, false otherwise.
=begin testing
use File::Compare;
my $src = '10e-smith-lib/db.t';
my $dest = '10e-smith-lib/db.t.copy';
rsync($src, $dest);
END { unlink $dest }
ok( -e $dest );
ok( compare($src, $dest) == 0, 'basic rsync copy' );
open(DEST, ">$dest" ) || die $!;
print DEST "Fooble\n";
close DEST;
# rsync in update-only mode. $dest is newer than $src and shouldn't
# be updated.
rsync($src, $dest, qw(--update));
ok( compare($src, $dest) == 1, 'rsync, update only' );
open(DEST, "$dest") || die $!;
my $data = join '', <DEST>;
close DEST;
is( $data, "Fooble\n" );
=end testing
=cut
our $RSYNC_CMD = '/usr/bin/rsync';
sub rsync {
my($src, $dest, @options) = @_;
push @options, '--quiet';
return !system($RSYNC_CMD, @options, $src, $dest);
}
=item B<rsync_ssh>
my $success = rsync_ssh($src, $dest, $user, $ident, \@rsync_opts,
\@ssh_opts);
Like rsync() except it uses ssh. A typical call might be:
rsync_ssh('some.host:', 'some/file', 'someuser', 'some/.ssh/identity',
[qw(--archive --delete)]);
=begin testing
use File::Compare;
my $src = '10e-smith-lib/db.t';
my $dest = '10e-smith-lib/db.t.copy';
unlink $dest;
END { unlink $dest }
no warnings 'once';
my @args;
my $real_rsync = \&esmith::util::system::rsync;
local *esmith::util::system::rsync = sub {
@args = @_;
pop @_;
$real_rsync->(@_);
};
rsync_ssh($src, $dest, 'someone', 'some/ident', [qw(--update)], [qw(-C)]);
ok( -e $dest );
ok( compare($src, $dest) == 0 );
is($args[0], $src );
is($args[1], $dest );
is($args[2], '--update' );
is($args[3], "-e $esmith::util::system::SSH_CMD -l someone -i some/ident -C");
=end testing
=cut
our $SSH_CMD = '/usr/bin/ssh';
sub rsync_ssh {
my($src, $dest, $user, $ident, $rsync_opts, $ssh_opts) = @_;
$ssh_opts ||= [];
my $ssh_opt = join ' ', ('-e', $SSH_CMD, '-l', $user, '-i', $ident,
@$ssh_opts);
return rsync($src, $dest, @$rsync_opts, $ssh_opt);
}
=head1 AUTHOR
Mitel Networks Corporation
=cut
1;