#!/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; }