preliminary self administration
[freeside.git] / fs_selfadmin / fs_mailadmin_server
diff --git a/fs_selfadmin/fs_mailadmin_server b/fs_selfadmin/fs_mailadmin_server
new file mode 100755 (executable)
index 0000000..1bcc42e
--- /dev/null
@@ -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;
+}
+