preliminary self administration
[freeside.git] / fs_selfadmin / fs_mailadmin_server
1 #!/usr/bin/perl -Tw
2 #
3 # fs_mailadmin_server
4 #
5
6 use strict;
7 use IO::Handle;
8 use FS::SSH qw(sshopen2);
9 use FS::UID qw(adminsuidsetup);
10 use FS::Conf;
11 use FS::Record qw( qsearch qsearchs );
12 use FS::cust_main_county;
13 use FS::cust_main;
14 use FS::svc_acct_admin;
15
16 use vars qw( $opt $Debug $conf $default_domain );
17
18 $Debug = 1;
19
20 my @payby = qw(CARD PREPAY);
21
22 my $user = shift or die &usage;
23 &adminsuidsetup( $user ); 
24
25 $conf = new FS::Conf;
26 $default_domain = $conf->config('domain');
27
28 my $machine = shift or die &usage;
29
30 my $agentnum = shift or die &usage;
31 my $agent = qsearchs( 'agent', { 'agentnum' => $agentnum } ) or die &usage;
32 my $pkgpart = $agent->pkgpart_hashref;
33
34 my $refnum = shift or die &usage;
35
36 #causing trouble for some folks
37 #$SIG{CHLD} = sub { wait() };
38
39 my($fs_mailadmind)=$conf->config('fs_mailadmind');
40
41 while (1) {
42   my($reader,$writer)=(new IO::Handle, new IO::Handle);
43   $writer->autoflush(1);
44   warn "[fs_mailadmin_server] Connecting to $machine...\n" if $Debug;
45   sshopen2($machine,$reader,$writer,$fs_mailadmind);
46
47   my $data;
48
49   warn "[fs_mailadmin_server] Sending locales...\n" if $Debug;
50   my @cust_main_county = qsearch('cust_main_county', {} );
51   print $writer $data = join("\n",
52     ( scalar(@cust_main_county) || die "no tax rates (cust_main_county records)" ),
53     map {
54       $_->taxnum,
55       $_->state,
56       $_->county,
57       $_->country,
58     } @cust_main_county
59   ),"\n";
60   warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
61
62   warn "[fs_mailadmin_server] Sending package definitions...\n" if $Debug;
63   my @part_pkg = grep { $_->svcpart('svc_acct') && $pkgpart->{ $_->pkgpart } }
64     qsearch( 'part_pkg', {} );
65   print $writer $data = join("\n",
66     ( scalar(@part_pkg) || die "no usable package definitions, agent $agentnum" ),
67     map {
68       $_->pkgpart,
69       $_->pkg,
70     } @part_pkg
71   ), "\n";
72   warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
73
74   warn "[fs_mailadmin_server] Sending POPs...\n" if $Debug;
75   my @svc_acct_pop = qsearch ('svc_acct_pop',{} );
76   print $writer $data = join("\n",
77     ( scalar(@svc_acct_pop) || die "No points of presence (svc_acct_pop records)" ),
78     map {
79       $_->popnum,
80       $_->city,
81       $_->state,
82       $_->ac,
83       $_->exch,
84       $_->loc,
85     } @svc_acct_pop
86   ), "\n";
87   warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
88
89   warn "[fs_mailadmin_server] Entering main loop...\n" if $Debug;
90 COMMAND:  while (1) {
91     warn "[fs_mailadmin_server] Reading (waiting for) command...\n" if $Debug;
92     chop( my($command, $user) = map { scalar(<$reader>) } ( 1 .. 2 ) );
93     my $domain = $default_domain;
94     $user =~ /^([\w\.\-]+)\@(([\w\-]+\.)+\w+)$/;
95     ($user, $domain) = ($1, $2);
96
97     if ($command eq 'authenticate'){
98       warn "[fs_mailadmin_server] Processing authenticate command for $user \n" if $Debug;
99       chop( my($password) = map { scalar(<$reader>) } ( 1 .. 1 ) );
100
101       my $error = '';
102
103       my @svc_domain = qsearchs('svc_domain', { 'domain'   => $domain });
104
105       if (scalar(@svc_domain) != 1) {
106         warn "Nonexistant or duplicate service account for \"$domain\"";
107         next COMMAND;
108       }
109
110       my @svc_acct = qsearchs('svc_acct', { 'username' => $user,
111                                             'domsvc'   => $svc_domain[0]->svcnum });
112       if (scalar(@svc_acct) != 1) {
113         die "Nonexistant or duplicate service account for \"$user\"";
114         next COMMAND;
115       }
116
117       if ($svc_acct[0]->_password eq $password) {
118         $error = "$user\@$domain OK";
119       }else{
120         $error = "$user\@$domain FAILED";
121       }
122       warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
123       print $writer $error, "\n";
124     }
125     elsif ($command eq 'list_packages'){
126       warn "[fs_mailadmin_server] Processing list_packages command for $user \n" if $Debug;
127
128       my $error = '';
129
130       my @packages = eval {find_administrable_packages( $user, $domain )};
131       warn "$@" if $@; 
132
133       my %packages;
134       my %accounts;
135
136       foreach my $package (@packages) {
137         $packages{my $pkgnum = $package->getfield('pkgnum')} = $default_domain;
138         $accounts{$pkgnum} = 0;
139         my @services = qsearch('cust_svc', { 'pkgnum' => $pkgnum });
140         foreach my $service (@services) {
141           if ($service->getfield('svcpart') eq '4'){
142             my $account=qsearchs('svc_domain', { 'svcnum' => $service->getfield('svcnum') });
143             $packages{$pkgnum}=$account->getfield('domain');
144             $accounts{$pkgnum}=$account->getfield('svcnum');
145           }
146         }
147       }
148       
149       print $writer $data = join("\n",
150         ( scalar(keys(%packages)) ),
151         map {
152           $_,
153           $packages{$_},
154           $accounts{$_},
155         } keys(%packages)
156       ), "\n";
157       warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
158
159     }elsif ($command eq 'list_mailboxes'){
160
161       warn "[fs_mailadmin_server] Processing list_mailboxes command for $user" if $Debug;
162       chop( my($pkgnum) = map { scalar(<$reader>) } ( 1 .. 1 ) );
163       warn "package $pkgnum \n" if $Debug;
164
165       my $error = '';
166
167       my @packages = eval {find_administrable_packages( $user, $domain )};
168       warn "$@" if $@; 
169
170       my @accounts;
171
172       foreach my $package (@packages) {
173         next unless ($pkgnum eq $package->getfield('pkgnum'));
174         my @services = qsearch('cust_svc', { 'pkgnum' => $package->getfield('pkgnum') });
175         foreach my $service (@services) {
176           if ($service->getfield('svcpart') eq '2'){
177             my $account=qsearchs('svc_acct', { 'svcnum' => $service->getfield('svcnum') });
178 #           $accounts[$#accounts+1]=$account->getfield('username');
179             $accounts[$#accounts+1]=$account;
180           }
181         }
182       }
183       
184       print $writer $data = join("\n",
185 #        ( scalar(@accounts) || die "No accounts (svc_acct records)" ),
186         ( scalar(@accounts) ),
187         map {
188           $_->svcnum,
189 #          $_->username,
190           $_->email,
191 #          $_->_password,
192           '*****',
193         } @accounts
194       ), "\n";
195       warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
196
197       
198     } elsif ($command eq 'delete_mailbox'){
199       warn "[fs_mailadmin_server] Processing delete_mailbox command for $user " if $Debug;
200       chop( my($account) = map { scalar(<$reader>) } ( 1 .. 1 ) );
201       warn "account $account \n" if $Debug;
202
203       my $error = '';
204
205       my @packages = eval { find_administrable_packages($user, $domain) };
206       warn "$@" if $@; 
207       $error ||= "$@" if $@; 
208
209       my @svc_acct = qsearchs('svc_acct', { 'svcnum' => $account }) unless $error;
210       if (scalar(@svc_acct) != 1) { $error ||= 'Nonexistant or duplicate service account for user.' };
211       if (! $error && check_administrator(\@packages, $svc_acct[0])){
212 # not sure about the next three lines... do we delete? or return error
213         foreach my $svc_forward (qsearch('svc_forward', { 'dstsvc' => $svc_acct[0]->getfield('svcnum') })) {
214           $error ||= $svc_forward->delete;
215         }
216         foreach my $svc_forward (qsearch('svc_forward', { 'srcsvc' => $svc_acct[0]->getfield('svcnum') })) {
217           $error ||= $svc_forward->delete;
218         }
219         $error ||= $svc_acct[0]->delete;
220       } else {
221         $error ||= "Illegal attempt to remove service";
222       }
223
224       
225       warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
226       print $writer $error, "\n";
227       
228     } elsif ($command eq 'password_mailbox'){
229       warn "[fs_mailadmin_server] Processing password_mailbox command for $user " if $Debug;
230       chop( my($account, $_password) = map { scalar(<$reader>) } ( 1 .. 2 ) );
231       warn "account $account with password $_password \n" if $Debug;
232
233       my $error = '';
234
235       my @packages = eval { find_administrable_packages($user, $domain) };
236       warn "$@" if $@; 
237       $error ||= "$@" if $@; 
238
239       my @svc_acct = qsearchs('svc_acct', { 'svcnum' => $account }) unless $error;
240       if (scalar(@svc_acct) != 1) { $error ||= 'Nonexistant or duplicate service account.' };
241
242       if (! $error && check_administrator(\@packages, $svc_acct[0])){
243         my $new = new FS::svc_acct ({$svc_acct[0]->hash});
244         $new->setfield('_password' => $_password);
245         $error ||= $new->replace($svc_acct[0]);
246       } else {
247         $error ||= "Illegal attempt to change password";
248       }
249
250       
251       warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
252       print $writer $error, "\n";
253       
254     } elsif ($command eq 'add_mailbox'){
255       warn "[fs_mailadmin_server] Processing add_mailbox command for $user " if $Debug;
256       chop( my($target_package, $account, $_password) = map { scalar(<$reader>) } ( 1 .. 3 ) );
257       warn "in package $target_package account $account with password $_password \n" if $Debug;
258
259       my $found_package;
260       my $domainsvc=0;
261       my $svcpart=2;    # this is 'email box'
262       my $svcpartsm=3;  # this is 'domain alias'
263       my $error = '';
264       my $found = 0;
265
266       my @packages = eval { find_administrable_packages($user, $domain) };
267       warn "$@" if $@; 
268       $error ||= "$@" if $@; 
269
270       foreach my $package (@packages) {
271         if ($package->getfield('pkgnum') eq $target_package) {
272           $found = 1;
273           $found_package=$package;
274           my @services = qsearch('cust_svc', { 'pkgnum' => $target_package });
275           foreach my $service (@services) {
276             if ($service->getfield('svcpart') eq '4'){
277               my @svc_domain=qsearchs('svc_domain', { 'svcnum' => $service->getfield('svcnum') });
278               if (scalar(@svc_domain) eq 1) {
279                 $domainsvc=$svc_domain[0]->getfield('svcnum');
280               }
281             }
282           }
283           last;
284         }
285       }
286       warn "User $user does not have administration rights to package $target_package\n" unless $found;
287       $error ||= "User $user does not have administration rights to package $target_package\n" unless $found;
288
289       my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$found_package->getfield('pkgpart')});
290
291       #list of services this pkgpart includes (although at the moment we only care
292       #  about $svcpart
293       my $pkg_svc;
294       my %pkg_svc = ();
295       foreach $pkg_svc ( qsearch('pkg_svc',{'pkgpart'=> $found_package->pkgpart }) ) {
296         $pkg_svc{$pkg_svc->svcpart} = $pkg_svc->quantity if $pkg_svc->quantity;
297       }
298
299       my @services = qsearch('cust_svc', {'pkgnum'  => $found_package->getfield('pkgnum'),
300                                           'svcpart' => $svcpart,
301                                          });
302
303       if (scalar(@services) >= $pkg_svc{$svcpart}) {
304         $error="Maximum allowed already reached.";
305       }
306       
307       my $svc_acct = new FS::svc_acct ( {
308         'pkgnum'    => $found_package->pkgnum,
309         'svcpart'   => $svcpart,
310         'username'  => $account,
311         'domsvc'    => $domainsvc,
312         '_password' => $_password,
313       } );
314
315       my $y = $svc_acct->setdefault; # arguably should be in new method
316       $error ||= $y unless ref($y);
317       #and just in case you were silly
318       $svc_acct->pkgnum($found_package->pkgnum);
319       $svc_acct->svcpart($svcpart);
320       $svc_acct->username($account);
321       $svc_acct->domsvc($domainsvc);
322       $svc_acct->_password($_password);
323
324       $error ||= $svc_acct->check;
325
326       if ( ! $error ) { #in this case, $cust_pkg should always
327                                      #be definied, but....
328         $error ||= $svc_acct->insert;
329         warn "WARNING: $error on pre-checked svc_acct record!" if $error;
330       }
331
332       warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
333       print $writer $error, "\n";
334       
335     }elsif ($command eq 'list_forwards'){
336
337       warn "[fs_mailadmin_server] Processing list_forwards command for $user" if $Debug;
338       chop( my($svcnum) = map { scalar(<$reader>) } ( 1 .. 1 ) );
339       warn "service $svcnum \n" if $Debug;
340
341       my $error = '';
342
343       my @packages = eval {find_administrable_packages( $user, $domain )};
344       warn "$@" if $@; 
345
346       my @forwards;
347
348       foreach my $package (@packages) {
349 #        next unless ($pkgnum eq $package->getfield('pkgnum'));
350         my @services = qsearch('cust_svc', { 'pkgnum' => $package->getfield('pkgnum') });
351         foreach my $service (@services) {
352           if ($service->getfield('svcpart') eq '10'){
353             my $forward=qsearchs('svc_forward', { 'svcnum' => $service->getfield('svcnum') });
354             $forwards[$#forwards+1]=$forward if ($forward->getfield('srcsvc') == $svcnum);
355           }
356         }
357       }
358       
359       print $writer $data = join("\n",
360         ( scalar(@forwards) ),
361         map {
362           $_->svcnum,
363           ($_->dstsvc ? qsearchs('svc_acct', {'svcnum' => $_->dstsvc})->email : $_->dst),
364         } @forwards
365       ), "\n";
366       warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
367
368       
369     }elsif ($command eq 'list_pkg_forwards'){
370
371       warn "[fs_mailadmin_server] Processing list_pkg_forwards command for $user" if $Debug;
372       chop( my($pkgnum) = map { scalar(<$reader>) } ( 1 .. 1 ) );
373       warn "package $pkgnum \n" if $Debug;
374
375       my $error = '';
376
377       my @packages = eval {find_administrable_packages( $user, $domain )};
378       warn "$@" if $@; 
379
380       my @forwards;
381
382       foreach my $package (@packages) {
383         next unless ($pkgnum eq $package->getfield('pkgnum'));
384         my @services = qsearch('cust_svc', { 'pkgnum' => $package->getfield('pkgnum') });
385         foreach my $service (@services) {
386           if ($service->getfield('svcpart') eq '10'){
387             my $forward=qsearchs('svc_forward', { 'svcnum' => $service->getfield('svcnum') });
388             $forwards[$#forwards+1]=$forward;
389           }
390         }
391       }
392       
393       print $writer $data = join("\n",
394         ( scalar(@forwards) ),
395         map {
396           $_->svcnum,
397           $_->srcsvc,
398           ($_->dstsvc ? qsearchs('svc_acct', {'svcnum' => $_->dstsvc})->email : $_->dst),
399         } @forwards
400       ), "\n";
401       warn "[fs_mailadmin_server] $data\n" if $Debug > 2;
402
403       
404     } elsif ($command eq 'delete_forward'){
405       warn "[fs_mailadmin_server] Processing delete_forward command for $user " if $Debug;
406       chop( my($forward) = map { scalar(<$reader>) } ( 1 .. 1 ) );
407       warn "forward $forward \n" if $Debug;
408
409       my $error = '';
410
411       my @packages = eval { find_administrable_packages($user, $domain) };
412       warn "$@" if $@; 
413       $error ||= "$@" if $@; 
414
415       my @svc_forward = qsearchs('svc_forward', { 'svcnum' => $forward }) unless $error;
416       if (scalar(@svc_forward) != 1) { $error ||= 'Nonexistant or duplicate service account for user.' };
417       if (! $error && check_administrator(\@packages, $svc_forward[0])){
418 # not sure about the next three lines... do we delete? or return error
419         $error ||= $svc_forward[0]->delete;
420       } else {
421         $error ||= "Illegal attempt to remove service";
422       }
423
424       
425       warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
426       print $writer $error, "\n";
427       
428     } elsif ($command eq 'add_forward'){
429       warn "[fs_mailadmin_server] Processing add_forward command for $user " if $Debug;
430       chop( my($target_package, $source, $dest) = map { scalar(<$reader>) } ( 1 .. 3 ) );
431       warn "in package $target_package source $source with destination $dest \n" if $Debug;
432
433       my $found_package;
434       my $domainsvc=0;
435       my $svcpart=10;   # this is 'forward service'
436       my $error = '';
437       my $found = 0;
438
439       my @packages = eval { find_administrable_packages($user, $domain) };
440       warn "$@" if $@; 
441       $error ||= "$@" if $@; 
442
443       foreach my $package (@packages) {
444         if ($package->getfield('pkgnum') eq $target_package) {
445           $found = 1;
446           $found_package=$package;
447           last;
448         }
449       }
450       warn "User $user does not have administration rights to package $target_package\n" unless $found;
451       $error ||= "User $user does not have administration rights to package $target_package\n" unless $found;
452
453       my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $source });
454       warn "Forwarding source $source does not exist.\n" unless $svc_acct;
455       $error ||= "Forwarding source $source does not exist.\n" unless $svc_acct;
456
457       my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $source });
458       warn "Forwarding source $source not attached to any account.\n" unless $cust_svc;
459       $error ||= "Forwarding source $source not attached to any account.\n" unless $cust_svc;
460
461       if ( ! $error ) {
462         warn "Forwarding source $source is not in package $target_package\n"
463           unless ($cust_svc->getfield('pkgnum') == $target_package);
464         $error ||= "Forwarding source $source is not in package $target_package\n"
465           unless ($cust_svc->getfield('pkgnum') == $target_package);
466       }
467
468       my $part_pkg = qsearchs('part_pkg',{'pkgpart'=>$found_package->getfield('pkgpart')});
469
470       #list of services this pkgpart includes (although at the moment we only care
471       #  about $svcpart
472       my $pkg_svc;
473       my %pkg_svc = ();
474       foreach $pkg_svc ( qsearch('pkg_svc',{'pkgpart'=> $found_package->pkgpart }) ) {
475         $pkg_svc{$pkg_svc->svcpart} = $pkg_svc->quantity if $pkg_svc->quantity;
476       }
477
478       my @services = qsearch('cust_svc', {'pkgnum'  => $found_package->getfield('pkgnum'),
479                                           'svcpart' => $svcpart,
480                                          });
481
482       if (scalar(@services) >= $pkg_svc{$svcpart}) {
483         $error="Maximum allowed already reached.";
484       }
485       
486       my $svc_forward = new FS::svc_forward ( {
487         'pkgnum'    => $found_package->pkgnum,
488         'svcpart'   => $svcpart,
489         'srcsvc'  => $source,
490         'dstsvc'    => 0,
491         'dst' => $dest,
492       } );
493
494       my $y = $svc_forward->setdefault; # arguably should be in new method
495       $error ||= $y unless ref($y);
496       #and just in case you were silly
497       $svc_forward->pkgnum($found_package->pkgnum);
498       $svc_forward->svcpart($svcpart);
499       $svc_forward->srcsvc($source);
500       $svc_forward->dstsvc(0);
501       $svc_forward->dst($dest);
502
503       $error ||= $svc_forward->check;
504
505       if ( ! $error ) { #in this case, $cust_pkg should always
506                                      #be definied, but....
507         $error ||= $svc_forward->insert;
508         warn "WARNING: $error on pre-checked svc_forward record!" if $error;
509       }
510
511       warn "[fs_mailadmin_server] Sending results...\n" if $Debug;
512       print $writer $error, "\n";
513       
514     } else {
515       warn "[fs_mailadmin_server] Bad command: $command \n" if $Debug;
516       print $writer "Bad command \n";
517     }
518   }
519   close $writer;
520   close $reader;
521   warn "connection to $machine lost!  waiting 60 seconds...\n";
522   sleep 60;
523   warn "reconnecting...\n";
524 }
525
526 sub usage {
527   die "Usage:\n\n  fs_mailadmin_server user machine agentnum refnum\n";
528 }
529
530 #sub find_administrable_packages {
531 #      my $user = shift;
532 #
533 #      my $error = '';
534 #
535 #      my @svc_acct = qsearchs('svc_acct', { 'username' => $user });
536 #      if (scalar(@svc_acct) != 1) {
537 #        die "Nonexistant or duplicate service account for \"$user\"";
538 #      }
539 #
540 #      my @cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_acct[0]->getfield('svcnum') });
541 #      if (scalar(@cust_svc) != 1 ) {
542 #        die "Nonexistant or duplicate customer service for \"$user\"";
543 #      }
544 #
545 #      my @cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_svc[0]->getfield('pkgnum') });
546 #      if (scalar(@cust_pkg) != 1) {
547 #        die "Nonexistant or duplicate customer package for \"$user\"";
548 #      }
549 #
550 #      my @cust_main = qsearchs('cust_main', { 'custnum' => $cust_pkg[0]->getfield('custnum') });
551 #      if (scalar(@cust_main) != 1 ) {
552 #        die "Nonexistant or duplicate customer for \"$user\"";
553 #      }
554 #
555 #      my @packages = $cust_main[0]->ncancelled_pkgs;
556 #}
557
558 sub find_administrable_packages {
559       my $user = shift;
560       my $domain = shift;
561
562       my @packages;
563       my $error = '';
564
565       my @svc_domain = qsearchs('svc_domain', { 'domain'   => $domain });
566
567       if (scalar(@svc_domain) != 1) {
568         die "Nonexistant or duplicate service account for \"$domain\"";
569       }
570
571       my @svc_acct = qsearchs('svc_acct', { 'username' => $user,
572                                             'domsvc'   => $svc_domain[0]->svcnum });
573       if (scalar(@svc_acct) != 1) {
574         die "Nonexistant or duplicate service account for \"$user\"";
575       }
576
577       my @svc_acct_admin = qsearch('svc_acct_admin', {'adminsvc' => $svc_acct[0]->getfield('svcnum') });
578       die "Nonexistant or duplicate customer service for \"$user\"" unless scalar(@svc_acct_admin);
579
580       foreach my $svc_acct_admin (@svc_acct_admin) {
581         my @cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_acct_admin->getfield('svcnum') });
582         if (scalar(@cust_svc) != 1 ) {
583           die "Nonexistant or duplicate customer service for admin \"$svc_acct_admin->getfield('svcnum')\"";
584         }
585
586         my @cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_svc[0]->getfield('pkgnum') });
587         if (scalar(@cust_pkg) != 1) {
588           die "Nonexistant or duplicate customer package for admin \"$user\"";
589         }
590
591         push @packages, $cust_pkg[0] unless $cust_pkg[0]->getfield('cancel');
592
593       }
594       (@packages);
595 }
596
597 sub check_administrator {
598       my ($allowed_packages_aref, $svc_acct_ref) = @_;
599
600       my $error = '';
601       my $found = 0;
602
603       {
604         my @cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_acct_ref->getfield('svcnum') });
605         if (scalar(@cust_svc) != 1 ) {
606           warn "Nonexistant or duplicate customer service for \"$svc_acct_ref->getfield('username')\"";
607           last;
608         }
609
610         my @cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $cust_svc[0]->getfield('pkgnum') });
611         if (scalar(@cust_pkg) != 1) {
612           warn "Nonexistant or duplicate customer package for \"$svc_acct_ref->getfield('username')\"";
613           last;
614         }
615
616         foreach my $package (@$allowed_packages_aref) {
617           if ($package->getfield('pkgnum') eq $cust_pkg[0]->getfield('pkgnum')) {
618             $found = 1;
619             last;
620           }
621         }
622       }
623
624       $found;
625 }
626
627 sub check_add {
628       my ($allowed_packages_aref, $target_package) = @_;
629
630       my $error = '';
631       my $found = 0;
632
633       foreach my $package (@$allowed_packages_aref) {
634         if ($package->getfield('pkgnum') eq $target_package) {
635           $found = 1;
636           last;
637         }
638       }
639
640       $found;
641 }
642