X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=fs_selfadmin%2Ffs_mailadmin_server;fp=fs_selfadmin%2Ffs_mailadmin_server;h=1bcc42e5da51703837abd54f16e972d223165978;hp=0000000000000000000000000000000000000000;hb=fba31d6a0954ccfbb1d491ec018f0513d2a4ee2a;hpb=fcb5658290eb457f0b2493b405c152a9cc1ad5a4 diff --git a/fs_selfadmin/fs_mailadmin_server b/fs_selfadmin/fs_mailadmin_server new file mode 100755 index 000000000..1bcc42e5d --- /dev/null +++ b/fs_selfadmin/fs_mailadmin_server @@ -0,0 +1,642 @@ +#!/usr/bin/perl -Tw +# +# fs_mailadmin_server +# + +use strict; +use IO::Handle; +use FS::SSH qw(sshopen2); +use FS::UID qw(adminsuidsetup); +use FS::Conf; +use FS::Record qw( qsearch qsearchs ); +use FS::cust_main_county; +use FS::cust_main; +use FS::svc_acct_admin; + +use vars qw( $opt $Debug $conf $default_domain ); + +$Debug = 1; + +my @payby = qw(CARD PREPAY); + +my $user = shift or die &usage; +&adminsuidsetup( $user ); + +$conf = new FS::Conf; +$default_domain = $conf->config('domain'); + +my $machine = shift or die &usage; + +my $agentnum = shift or die &usage; +my $agent = qsearchs( 'agent', { 'agentnum' => $agentnum } ) or die &usage; +my $pkgpart = $agent->pkgpart_hashref; + +my $refnum = shift or die &usage; + +#causing trouble for some folks +#$SIG{CHLD} = sub { wait() }; + +my($fs_mailadmind)=$conf->config('fs_mailadmind'); + +while (1) { + my($reader,$writer)=(new IO::Handle, new IO::Handle); + $writer->autoflush(1); + warn "[fs_mailadmin_server] Connecting to $machine...\n" if $Debug; + sshopen2($machine,$reader,$writer,$fs_mailadmind); + + my $data; + + warn "[fs_mailadmin_server] Sending locales...\n" if $Debug; + my @cust_main_county = qsearch('cust_main_county', {} ); + print $writer $data = join("\n", + ( scalar(@cust_main_county) || die "no tax rates (cust_main_county records)" ), + map { + $_->taxnum, + $_->state, + $_->county, + $_->country, + } @cust_main_county + ),"\n"; + warn "[fs_mailadmin_server] $data\n" if $Debug > 2; + + warn "[fs_mailadmin_server] Sending package definitions...\n" if $Debug; + my @part_pkg = grep { $_->svcpart('svc_acct') && $pkgpart->{ $_->pkgpart } } + qsearch( 'part_pkg', {} ); + print $writer $data = join("\n", + ( scalar(@part_pkg) || die "no usable package definitions, agent $agentnum" ), + map { + $_->pkgpart, + $_->pkg, + } @part_pkg + ), "\n"; + warn "[fs_mailadmin_server] $data\n" if $Debug > 2; + + warn "[fs_mailadmin_server] Sending POPs...\n" if $Debug; + my @svc_acct_pop = qsearch ('svc_acct_pop',{} ); + print $writer $data = join("\n", + ( scalar(@svc_acct_pop) || die "No points of presence (svc_acct_pop records)" ), + map { + $_->popnum, + $_->city, + $_->state, + $_->ac, + $_->exch, + $_->loc, + } @svc_acct_pop + ), "\n"; + warn "[fs_mailadmin_server] $data\n" if $Debug > 2; + + warn "[fs_mailadmin_server] Entering main loop...\n" if $Debug; +COMMAND: while (1) { + warn "[fs_mailadmin_server] Reading (waiting for) command...\n" if $Debug; + chop( my($command, $user) = map { scalar(<$reader>) } ( 1 .. 2 ) ); + my $domain = $default_domain; + $user =~ /^([\w\.\-]+)\@(([\w\-]+\.)+\w+)$/; + ($user, $domain) = ($1, $2); + + if ($command eq 'authenticate'){ + warn "[fs_mailadmin_server] Processing authenticate command for $user \n" if $Debug; + chop( my($password) = map { scalar(<$reader>) } ( 1 .. 1 ) ); + + my $error = ''; + + my @svc_domain = qsearchs('svc_domain', { 'domain' => $domain }); + + if (scalar(@svc_domain) != 1) { + warn "Nonexistant or duplicate service account for \"$domain\""; + next COMMAND; + } + + my @svc_acct = qsearchs('svc_acct', { 'username' => $user, + 'domsvc' => $svc_domain[0]->svcnum }); + if (scalar(@svc_acct) != 1) { + die "Nonexistant or duplicate service account for \"$user\""; + next COMMAND; + } + + if ($svc_acct[0]->_password eq $password) { + $error = "$user\@$domain OK"; + }else{ + $error = "$user\@$domain FAILED"; + } + warn "[fs_mailadmin_server] Sending results...\n" if $Debug; + print $writer $error, "\n"; + } + elsif ($command eq 'list_packages'){ + warn "[fs_mailadmin_server] Processing list_packages command for $user \n" if $Debug; + + my $error = ''; + + my @packages = eval {find_administrable_packages( $user, $domain )}; + warn "$@" if $@; + + my %packages; + my %accounts; + + foreach my $package (@packages) { + $packages{my $pkgnum = $package->getfield('pkgnum')} = $default_domain; + $accounts{$pkgnum} = 0; + my @services = qsearch('cust_svc', { 'pkgnum' => $pkgnum }); + foreach my $service (@services) { + if ($service->getfield('svcpart') eq '4'){ + my $account=qsearchs('svc_domain', { 'svcnum' => $service->getfield('svcnum') }); + $packages{$pkgnum}=$account->getfield('domain'); + $accounts{$pkgnum}=$account->getfield('svcnum'); + } + } + } + + print $writer $data = join("\n", + ( scalar(keys(%packages)) ), + map { + $_, + $packages{$_}, + $accounts{$_}, + } keys(%packages) + ), "\n"; + warn "[fs_mailadmin_server] $data\n" if $Debug > 2; + + }elsif ($command eq 'list_mailboxes'){ + + warn "[fs_mailadmin_server] Processing list_mailboxes command for $user" if $Debug; + chop( my($pkgnum) = map { scalar(<$reader>) } ( 1 .. 1 ) ); + warn "package $pkgnum \n" if $Debug; + + my $error = ''; + + my @packages = eval {find_administrable_packages( $user, $domain )}; + warn "$@" if $@; + + my @accounts; + + foreach my $package (@packages) { + next unless ($pkgnum eq $package->getfield('pkgnum')); + my @services = qsearch('cust_svc', { 'pkgnum' => $package->getfield('pkgnum') }); + foreach my $service (@services) { + if ($service->getfield('svcpart') eq '2'){ + my $account=qsearchs('svc_acct', { 'svcnum' => $service->getfield('svcnum') }); +# $accounts[$#accounts+1]=$account->getfield('username'); + $accounts[$#accounts+1]=$account; + } + } + } + + print $writer $data = join("\n", +# ( scalar(@accounts) || die "No accounts (svc_acct records)" ), + ( scalar(@accounts) ), + map { + $_->svcnum, +# $_->username, + $_->email, +# $_->_password, + '*****', + } @accounts + ), "\n"; + warn "[fs_mailadmin_server] $data\n" if $Debug > 2; + + + } elsif ($command eq 'delete_mailbox'){ + warn "[fs_mailadmin_server] Processing delete_mailbox command for $user " if $Debug; + chop( my($account) = map { scalar(<$reader>) } ( 1 .. 1 ) ); + warn "account $account \n" if $Debug; + + my $error = ''; + + my @packages = eval { find_administrable_packages($user, $domain) }; + warn "$@" if $@; + $error ||= "$@" if $@; + + my @svc_acct = qsearchs('svc_acct', { 'svcnum' => $account }) unless $error; + if (scalar(@svc_acct) != 1) { $error ||= 'Nonexistant or duplicate service account for user.' }; + if (! $error && check_administrator(\@packages, $svc_acct[0])){ +# not sure about the next three lines... do we delete? or return error + foreach my $svc_forward (qsearch('svc_forward', { 'dstsvc' => $svc_acct[0]->getfield('svcnum') })) { + $error ||= $svc_forward->delete; + } + foreach my $svc_forward (qsearch('svc_forward', { 'srcsvc' => $svc_acct[0]->getfield('svcnum') })) { + $error ||= $svc_forward->delete; + } + $error ||= $svc_acct[0]->delete; + } else { + $error ||= "Illegal attempt to remove service"; + } + + + warn "[fs_mailadmin_server] Sending results...\n" if $Debug; + print $writer $error, "\n"; + + } elsif ($command eq 'password_mailbox'){ + warn "[fs_mailadmin_server] Processing password_mailbox command for $user " if $Debug; + chop( my($account, $_password) = map { scalar(<$reader>) } ( 1 .. 2 ) ); + warn "account $account with password $_password \n" if $Debug; + + my $error = ''; + + my @packages = eval { find_administrable_packages($user, $domain) }; + warn "$@" if $@; + $error ||= "$@" if $@; + + my @svc_acct = qsearchs('svc_acct', { 'svcnum' => $account }) unless $error; + if (scalar(@svc_acct) != 1) { $error ||= 'Nonexistant or duplicate service account.' }; + + if (! $error && check_administrator(\@packages, $svc_acct[0])){ + my $new = new FS::svc_acct ({$svc_acct[0]->hash}); + $new->setfield('_password' => $_password); + $error ||= $new->replace($svc_acct[0]); + } else { + $error ||= "Illegal attempt to change password"; + } + + + warn "[fs_mailadmin_server] Sending results...\n" if $Debug; + print $writer $error, "\n"; + + } elsif ($command eq 'add_mailbox'){ + warn "[fs_mailadmin_server] Processing add_mailbox command for $user " if $Debug; + chop( my($target_package, $account, $_password) = map { scalar(<$reader>) } ( 1 .. 3 ) ); + warn "in package $target_package account $account with password $_password \n" if $Debug; + + my $found_package; + my $domainsvc=0; + my $svcpart=2; # this is 'email box' + my $svcpartsm=3; # this is 'domain alias' + my $error = ''; + my $found = 0; + + my @packages = eval { find_administrable_packages($user, $domain) }; + warn "$@" if $@; + $error ||= "$@" if $@; + + foreach my $package (@packages) { + if ($package->getfield('pkgnum') eq $target_package) { + $found = 1; + $found_package=$package; + my @services = qsearch('cust_svc', { 'pkgnum' => $target_package }); + foreach my $service (@services) { + if ($service->getfield('svcpart') eq '4'){ + my @svc_domain=qsearchs('svc_domain', { 'svcnum' => $service->getfield('svcnum') }); + if (scalar(@svc_domain) eq 1) { + $domainsvc=$svc_domain[0]->getfield('svcnum'); + } + } + } + last; + } + } + warn "User $user does not have administration rights to package $target_package\n" unless $found; + $error ||= "User $user does not have administration rights to package $target_package\n" unless $found; + + my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$found_package->getfield('pkgpart')}); + + #list of services this pkgpart includes (although at the moment we only care + # about $svcpart + my $pkg_svc; + my %pkg_svc = (); + foreach $pkg_svc ( qsearch('pkg_svc',{'pkgpart'=> $found_package->pkgpart }) ) { + $pkg_svc{$pkg_svc->svcpart} = $pkg_svc->quantity if $pkg_svc->quantity; + } + + my @services = qsearch('cust_svc', {'pkgnum' => $found_package->getfield('pkgnum'), + 'svcpart' => $svcpart, + }); + + if (scalar(@services) >= $pkg_svc{$svcpart}) { + $error="Maximum allowed already reached."; + } + + my $svc_acct = new FS::svc_acct ( { + 'pkgnum' => $found_package->pkgnum, + 'svcpart' => $svcpart, + 'username' => $account, + 'domsvc' => $domainsvc, + '_password' => $_password, + } ); + + my $y = $svc_acct->setdefault; # arguably should be in new method + $error ||= $y unless ref($y); + #and just in case you were silly + $svc_acct->pkgnum($found_package->pkgnum); + $svc_acct->svcpart($svcpart); + $svc_acct->username($account); + $svc_acct->domsvc($domainsvc); + $svc_acct->_password($_password); + + $error ||= $svc_acct->check; + + if ( ! $error ) { #in this case, $cust_pkg should always + #be definied, but.... + $error ||= $svc_acct->insert; + warn "WARNING: $error on pre-checked svc_acct record!" if $error; + } + + warn "[fs_mailadmin_server] Sending results...\n" if $Debug; + print $writer $error, "\n"; + + }elsif ($command eq 'list_forwards'){ + + warn "[fs_mailadmin_server] Processing list_forwards command for $user" if $Debug; + chop( my($svcnum) = map { scalar(<$reader>) } ( 1 .. 1 ) ); + warn "service $svcnum \n" if $Debug; + + my $error = ''; + + my @packages = eval {find_administrable_packages( $user, $domain )}; + warn "$@" if $@; + + my @forwards; + + foreach my $package (@packages) { +# next unless ($pkgnum eq $package->getfield('pkgnum')); + my @services = qsearch('cust_svc', { 'pkgnum' => $package->getfield('pkgnum') }); + foreach my $service (@services) { + if ($service->getfield('svcpart') eq '10'){ + my $forward=qsearchs('svc_forward', { 'svcnum' => $service->getfield('svcnum') }); + $forwards[$#forwards+1]=$forward if ($forward->getfield('srcsvc') == $svcnum); + } + } + } + + print $writer $data = join("\n", + ( scalar(@forwards) ), + map { + $_->svcnum, + ($_->dstsvc ? qsearchs('svc_acct', {'svcnum' => $_->dstsvc})->email : $_->dst), + } @forwards + ), "\n"; + warn "[fs_mailadmin_server] $data\n" if $Debug > 2; + + + }elsif ($command eq 'list_pkg_forwards'){ + + warn "[fs_mailadmin_server] Processing list_pkg_forwards command for $user" if $Debug; + chop( my($pkgnum) = map { scalar(<$reader>) } ( 1 .. 1 ) ); + warn "package $pkgnum \n" if $Debug; + + my $error = ''; + + my @packages = eval {find_administrable_packages( $user, $domain )}; + warn "$@" if $@; + + my @forwards; + + foreach my $package (@packages) { + next unless ($pkgnum eq $package->getfield('pkgnum')); + my @services = qsearch('cust_svc', { 'pkgnum' => $package->getfield('pkgnum') }); + foreach my $service (@services) { + if ($service->getfield('svcpart') eq '10'){ + my $forward=qsearchs('svc_forward', { 'svcnum' => $service->getfield('svcnum') }); + $forwards[$#forwards+1]=$forward; + } + } + } + + print $writer $data = join("\n", + ( scalar(@forwards) ), + map { + $_->svcnum, + $_->srcsvc, + ($_->dstsvc ? qsearchs('svc_acct', {'svcnum' => $_->dstsvc})->email : $_->dst), + } @forwards + ), "\n"; + warn "[fs_mailadmin_server] $data\n" if $Debug > 2; + + + } elsif ($command eq 'delete_forward'){ + warn "[fs_mailadmin_server] Processing delete_forward command for $user " if $Debug; + chop( my($forward) = map { scalar(<$reader>) } ( 1 .. 1 ) ); + warn "forward $forward \n" if $Debug; + + my $error = ''; + + my @packages = eval { find_administrable_packages($user, $domain) }; + warn "$@" if $@; + $error ||= "$@" if $@; + + my @svc_forward = qsearchs('svc_forward', { 'svcnum' => $forward }) unless $error; + if (scalar(@svc_forward) != 1) { $error ||= 'Nonexistant or duplicate service account for user.' }; + if (! $error && check_administrator(\@packages, $svc_forward[0])){ +# not sure about the next three lines... do we delete? or return error + $error ||= $svc_forward[0]->delete; + } else { + $error ||= "Illegal attempt to remove service"; + } + + + warn "[fs_mailadmin_server] Sending results...\n" if $Debug; + print $writer $error, "\n"; + + } elsif ($command eq 'add_forward'){ + warn "[fs_mailadmin_server] Processing add_forward command for $user " if $Debug; + chop( my($target_package, $source, $dest) = map { scalar(<$reader>) } ( 1 .. 3 ) ); + warn "in package $target_package source $source with destination $dest \n" if $Debug; + + my $found_package; + my $domainsvc=0; + my $svcpart=10; # this is 'forward service' + my $error = ''; + my $found = 0; + + my @packages = eval { find_administrable_packages($user, $domain) }; + warn "$@" if $@; + $error ||= "$@" if $@; + + foreach my $package (@packages) { + if ($package->getfield('pkgnum') eq $target_package) { + $found = 1; + $found_package=$package; + last; + } + } + warn "User $user does not have administration rights to package $target_package\n" unless $found; + $error ||= "User $user does not have administration rights to package $target_package\n" unless $found; + + my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $source }); + warn "Forwarding source $source does not exist.\n" unless $svc_acct; + $error ||= "Forwarding source $source does not exist.\n" unless $svc_acct; + + my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $source }); + warn "Forwarding source $source not attached to any account.\n" unless $cust_svc; + $error ||= "Forwarding source $source not attached to any account.\n" unless $cust_svc; + + if ( ! $error ) { + warn "Forwarding source $source is not in package $target_package\n" + unless ($cust_svc->getfield('pkgnum') == $target_package); + $error ||= "Forwarding source $source is not in package $target_package\n" + unless ($cust_svc->getfield('pkgnum') == $target_package); + } + + my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$found_package->getfield('pkgpart')}); + + #list of services this pkgpart includes (although at the moment we only care + # about $svcpart + my $pkg_svc; + my %pkg_svc = (); + foreach $pkg_svc ( qsearch('pkg_svc',{'pkgpart'=> $found_package->pkgpart }) ) { + $pkg_svc{$pkg_svc->svcpart} = $pkg_svc->quantity if $pkg_svc->quantity; + } + + my @services = qsearch('cust_svc', {'pkgnum' => $found_package->getfield('pkgnum'), + 'svcpart' => $svcpart, + }); + + if (scalar(@services) >= $pkg_svc{$svcpart}) { + $error="Maximum allowed already reached."; + } + + my $svc_forward = new FS::svc_forward ( { + 'pkgnum' => $found_package->pkgnum, + 'svcpart' => $svcpart, + 'srcsvc' => $source, + 'dstsvc' => 0, + 'dst' => $dest, + } ); + + my $y = $svc_forward->setdefault; # arguably should be in new method + $error ||= $y unless ref($y); + #and just in case you were silly + $svc_forward->pkgnum($found_package->pkgnum); + $svc_forward->svcpart($svcpart); + $svc_forward->srcsvc($source); + $svc_forward->dstsvc(0); + $svc_forward->dst($dest); + + $error ||= $svc_forward->check; + + if ( ! $error ) { #in this case, $cust_pkg should always + #be definied, but.... + $error ||= $svc_forward->insert; + warn "WARNING: $error on pre-checked svc_forward record!" if $error; + } + + warn "[fs_mailadmin_server] Sending results...\n" if $Debug; + print $writer $error, "\n"; + + } else { + warn "[fs_mailadmin_server] Bad command: $command \n" if $Debug; + print $writer "Bad command \n"; + } + } + close $writer; + close $reader; + warn "connection to $machine lost! waiting 60 seconds...\n"; + sleep 60; + warn "reconnecting...\n"; +} + +sub usage { + die "Usage:\n\n fs_mailadmin_server user machine agentnum refnum\n"; +} + +#sub find_administrable_packages { +# my $user = shift; +# +# my $error = ''; +# +# my @svc_acct = qsearchs('svc_acct', { 'username' => $user }); +# if (scalar(@svc_acct) != 1) { +# die "Nonexistant or duplicate service account for \"$user\""; +# } +# +# my @cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_acct[0]->getfield('svcnum') }); +# if (scalar(@cust_svc) != 1 ) { +# die "Nonexistant or duplicate customer service for \"$user\""; +# } +# +# my @cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_svc[0]->getfield('pkgnum') }); +# if (scalar(@cust_pkg) != 1) { +# die "Nonexistant or duplicate customer package for \"$user\""; +# } +# +# my @cust_main = qsearchs('cust_main', { 'custnum' => $cust_pkg[0]->getfield('custnum') }); +# if (scalar(@cust_main) != 1 ) { +# die "Nonexistant or duplicate customer for \"$user\""; +# } +# +# my @packages = $cust_main[0]->ncancelled_pkgs; +#} + +sub find_administrable_packages { + my $user = shift; + my $domain = shift; + + my @packages; + my $error = ''; + + my @svc_domain = qsearchs('svc_domain', { 'domain' => $domain }); + + if (scalar(@svc_domain) != 1) { + die "Nonexistant or duplicate service account for \"$domain\""; + } + + my @svc_acct = qsearchs('svc_acct', { 'username' => $user, + 'domsvc' => $svc_domain[0]->svcnum }); + if (scalar(@svc_acct) != 1) { + die "Nonexistant or duplicate service account for \"$user\""; + } + + my @svc_acct_admin = qsearch('svc_acct_admin', {'adminsvc' => $svc_acct[0]->getfield('svcnum') }); + die "Nonexistant or duplicate customer service for \"$user\"" unless scalar(@svc_acct_admin); + + foreach my $svc_acct_admin (@svc_acct_admin) { + my @cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_acct_admin->getfield('svcnum') }); + if (scalar(@cust_svc) != 1 ) { + die "Nonexistant or duplicate customer service for admin \"$svc_acct_admin->getfield('svcnum')\""; + } + + my @cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_svc[0]->getfield('pkgnum') }); + if (scalar(@cust_pkg) != 1) { + die "Nonexistant or duplicate customer package for admin \"$user\""; + } + + push @packages, $cust_pkg[0] unless $cust_pkg[0]->getfield('cancel'); + + } + (@packages); +} + +sub check_administrator { + my ($allowed_packages_aref, $svc_acct_ref) = @_; + + my $error = ''; + my $found = 0; + + { + my @cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_acct_ref->getfield('svcnum') }); + if (scalar(@cust_svc) != 1 ) { + warn "Nonexistant or duplicate customer service for \"$svc_acct_ref->getfield('username')\""; + last; + } + + my @cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_svc[0]->getfield('pkgnum') }); + if (scalar(@cust_pkg) != 1) { + warn "Nonexistant or duplicate customer package for \"$svc_acct_ref->getfield('username')\""; + last; + } + + foreach my $package (@$allowed_packages_aref) { + if ($package->getfield('pkgnum') eq $cust_pkg[0]->getfield('pkgnum')) { + $found = 1; + last; + } + } + } + + $found; +} + +sub check_add { + my ($allowed_packages_aref, $target_package) = @_; + + my $error = ''; + my $found = 0; + + foreach my $package (@$allowed_packages_aref) { + if ($package->getfield('pkgnum') eq $target_package) { + $found = 1; + last; + } + } + + $found; +} +