855651370b7389dd57e56afd4594d45a8b29a428
[freeside.git] / FS / FS / part_export / communigate_pro.pm
1 package FS::part_export::communigate_pro;
2
3 use strict;
4 use vars qw(@ISA %info %options %quotas $DEBUG);
5 use Data::Dumper;
6 use Tie::IxHash;
7 use FS::part_export;
8 use FS::queue;
9
10 @ISA = qw(FS::part_export);
11
12 $DEBUG = 1;
13
14 tie %options, 'Tie::IxHash',
15   'port'          => { label   =>'Port number', default=>'106', },
16   'login'         => { label   =>'The administrator account name.  The name can contain a domain part.', },
17   'password'      => { label   =>'The administrator account password.', },
18   'accountType'   => { label   => 'Type for newly-created accounts (default when not specified in service)',
19                        type    => 'select',
20                        options => [qw(MultiMailbox TextMailbox MailDirMailbox AGrade BGrade CGrade)],
21                        default => 'MultiMailbox',
22                      },
23   'externalFlag'  => { label   => 'Create accounts with an external (visible for legacy mailers) INBOX.',
24                        type    => 'checkbox',
25                      },
26   'AccessModes'   => { label   => 'Access modes (default when not specified in service)',
27                        default => 'Mail POP IMAP PWD WebMail WebSite',
28                      },
29   'create_domain' => { label   => 'Domain creation API call',
30                        type    => 'select',
31                        options => [qw( CreateDomain CreateSharedDomain )],
32                      }
33 ;
34
35 %info = (
36   'svc'     => [qw( svc_acct svc_domain svc_forward )],
37   'desc'    => 'Real-time export of accounts and domains to a CommuniGate Pro mail server',
38   'options' => \%options,
39   'notes'   => <<'END'
40 Real time export of accounts and domains to a
41 <a href="http://www.stalker.com/CommuniGatePro/">CommuniGate Pro</a>
42 mail server.  The
43 <a href="http://www.stalker.com/CGPerl/">CommuniGate Pro Perl Interface</a>
44 must be installed as CGP::CLI.
45 END
46 );
47
48 %quotas = (
49   'quota'        => 'MaxAccountSize',
50   'file_quota'   => 'MaxWebSize',
51   'file_maxnum'  => 'MaxWebFiles',
52   'file_maxsize' => 'MaxFileSize',
53 );
54
55 sub rebless { shift; }
56
57 sub export_username {
58   my($self, $svc_acct) = (shift, shift);
59   $svc_acct->email;
60 }
61
62 sub _export_insert {
63   my( $self, $svc_x ) = (shift, shift);
64
65   my $table = $svc_x->table;
66   my $method = "_export_insert_$table";
67   $self->$method($svc_x, @_);
68 }
69
70 sub _export_insert_svc_acct {
71   my( $self, $svc_acct ) = (shift, shift);
72
73   my %settings = (
74     'AccessModes'    => ( $svc_acct->cgp_accessmodes
75                           || $self->option('AccessModes') ),
76     'RealName'       => $svc_acct->finger,
77     'Password'       => $svc_acct->_password,
78     map { $quotas{$_} => $svc_acct->$_() }
79         grep $svc_acct->$_(), keys %quotas
80   );
81   #phase 2: pwdallowed, passwordrecovery, allowed mail rules,
82   # RPOP modifications, accepts mail to all, add trailer to sent mail
83   #phase 3: archive messages, mailing lists
84
85   my @options = ( 'CreateAccount',
86     'accountName'    => $self->export_username($svc_acct),
87     'accountType'    => ( $svc_acct->cgp_type
88                           || $self->option('accountType') ), 
89     'settings'       => \%settings
90   );
91
92   push @options, 'externalFlag'   => $self->option('externalFlag')
93     if $self->option('externalFlag');
94
95   #let's do the create realtime too, for much the same reasons, and to avoid
96   #pain of trying to queue w/dep the prefs & aliases
97   eval { $self->communigate_pro_runcommand( @options ) };
98   return $@ if $@;
99
100   #preferences
101   my %prefs = ();
102   $prefs{'DeleteMode'} = $svc_acct->cgp_deletemode if $svc_acct->cgp_deletemode;
103   $prefs{'EmptyTrash'} = $svc_acct->cgp_emptytrash if $svc_acct->cgp_emptytrash;
104   #phase 2: language, time zone, layout, pronto style, send read receipts
105   if ( keys %prefs ) {
106     my $pref_err = $self->communigate_pro_queue( $svc_acct->svcnum,
107       'UpdateAccountPrefs',
108       $self->export_username($svc_acct),
109       %prefs,
110     );
111    warn "WARNING: error queueing UpdateAccountPrefs job: $pref_err"
112     if $pref_err;
113   }
114
115   #aliases
116   if ( $svc_acct->cgp_aliases ) {
117     my $alias_err = $self->communigate_pro_queue( $svc_acct->svcnum,
118       'SetAccountAliases',
119       $self->export_username($svc_acct),
120       [ split(/\s*,\s*/, $svc_acct->cgp_aliases) ],
121     );
122     warn "WARNING: error queueing SetAccountAliases job: $alias_err"
123       if $alias_err;
124   }
125
126   '';
127
128 }
129
130 sub _export_insert_svc_domain {
131   my( $self, $svc_domain ) = (shift, shift);
132
133   my $create = $self->option('create_domain') || 'CreateDomain';
134
135   my @options = ( $svc_domain->svcnum, $create, $svc_domain->domain,
136     #other domain creation options?
137   );
138   push @options, 'AccountsLimit' => $svc_domain->max_accounts
139     if $svc_domain->max_accounts;
140
141   $self->communigate_pro_queue( @options );
142 }
143
144 sub _export_insert_svc_forward {
145   my( $self, $svc_forward ) = (shift, shift);
146
147   my $src = $svc_forward->src || $svc_forward->srcsvc_acct->email;
148   my $dst = $svc_forward->dst || $svc_forward->dstsvc_acct->email;
149
150   #real-time here, presuming CGP does some dup detection?
151   eval { $self->communigate_pro_runcommand( 'CreateForwarder', $src, $dst); };
152   return $@ if $@;
153
154   '';
155 }
156
157 sub _export_replace {
158   my( $self, $new, $old ) = (shift, shift, shift);
159
160   my $table = $new->table;
161   my $method = "_export_replace_$table";
162   $self->$method($new, $old, @_);
163 }
164
165 sub _export_replace_svc_acct {
166   my( $self, $new, $old ) = (shift, shift, shift);
167
168   #let's just do the rename part realtime rather than trying to queue
169   #w/dependencies.  we don't want FS winding up out-of-sync with the wrong
170   #username and a queued job anyway.  right??
171   if ( $self->export_username($old) ne $self->export_username($new) ) {
172     eval { $self->communigate_pro_runcommand(
173       'RenameAccount',
174       $self->export_username($old),
175       $self->export_username($new),
176     ) };
177     return $@ if $@;
178   }
179
180   if ( $new->_password ne $old->_password
181        && '*SUSPENDED* '.$old->_password ne $new->_password
182   ) {
183     $self->communigate_pro_queue( $new->svcnum, 'SetAccountPassword',
184                                   $self->export_username($new), $new->_password
185                                 );
186   }
187
188   my %settings = ();
189
190   $settings{'RealName'} = $new->finger
191     if $old->finger ne $new->finger;
192   $settings{$quotas{$_}} = $new->$_()
193     foreach grep $old->$_() ne $new->$_(), keys %quotas;
194   $settings{'accountType'} = $new->cgp_type
195     if $old->cgp_type ne $new->cgp_type;
196   $settings{'AccessModes'} = $new->cgp_accessmodes
197     if $old->cgp_accessmodes ne $new->cgp_accessmodes
198     || $old->cgp_type ne $new->cgp_type;
199
200   #phase 2: pwdallowed, passwordrecovery, allowed mail rules,
201   # RPOP modifications, accepts mail to all, add trailer to sent mail
202   #phase 3: archive messages, mailing lists
203
204   if ( keys %settings ) {
205     my $error = $self->communigate_pro_queue(
206       $new->svcnum,
207       'UpdateAccountSettings',
208       $self->export_username($new),
209       %settings,
210     );
211     return $error if $error;
212   }
213
214   #preferences
215   my %prefs = ();
216   $prefs{'DeleteMode'} = $new->cgp_deletemode
217     if $old->cgp_deletemode ne $new->cgp_deletemode;
218   $prefs{'EmptyTrash'} = $new->cgp_emptytrash
219     if $old->cgp_emptytrash ne $new->cgp_emptytrash;
220   #phase 2: language, time zone, layout, pronto style, send read receipts
221   if ( keys %prefs ) {
222     my $pref_err = $self->communigate_pro_queue( $new->svcnum,
223       'UpdateAccountPrefs',
224       $self->export_username($new),
225       %prefs,
226     );
227    warn "WARNING: error queueing UpdateAccountPrefs job: $pref_err"
228     if $pref_err;
229   }
230
231   if ( $old->cgp_aliases ne $new->cgp_aliases ) {
232     my $error = $self->communigate_pro_queue(
233       $new->svcnum,
234       'SetAccountAliases',
235       $self->export_username($new),
236       [ split(/\s*,\s*/, $new->cgp_aliases) ],
237     );
238     return $error if $error;
239   }
240
241   '';
242
243 }
244
245 sub _export_replace_svc_domain {
246   my( $self, $new, $old ) = (shift, shift, shift);
247
248   if ( $old->domain ne $new->domain ) {
249     my $error = $self->communigate_pro_queue( $new->svcnum, 'RenameDomain',
250       $old->domain, $new->domain,
251     );
252     return $error if $error;
253   }
254
255   if ( $old->max_accounts ne $new->max_accounts ) {
256     my $error = $self->communigate_pro_queue( $new->svcnum,
257       'UpdateDomainSettings',
258       $new->domain,
259       'AccountsLimit' => ($new->max_accounts || 'default'),
260     );
261     return $error if $error;
262   }
263
264   #other kinds of changes?
265
266   '';
267 }
268
269 sub _export_replace_svc_forward {
270   my( $self, $new, $old ) = (shift, shift, shift);
271
272   my $osrc = $old->src || $old->srcsvc_acct->email;
273   my $nsrc = $new->src || $new->srcsvc_acct->email;
274   my $odst = $old->dst || $old->dstsvc_acct->email;
275   my $ndst = $new->dst || $new->dstsvc_acct->email;
276
277   if ( $odst ne $ndst ) {
278
279     #no change command, so delete and create (real-time)
280     eval { $self->communigate_pro_runcommand('DeleteForwarder', $osrc) };
281     return $@ if $@;
282     eval { $self->communigate_pro_runcommand('CreateForwarder', $nsrc, $ndst)};
283     return $@ if $@;
284
285   } elsif ( $osrc ne $nsrc ) {
286
287     #real-time here, presuming CGP does some dup detection?
288     eval { $self->communigate_pro_runcommand( 'RenameForwarder', $osrc, $nsrc)};
289     return $@ if $@;
290
291   } else {
292     warn "communigate replace called for svc_forward with no changes\n";#confess
293   }
294
295   '';
296 }
297
298 sub _export_delete {
299   my( $self, $svc_x ) = (shift, shift);
300
301   my $table = $svc_x->table;
302   my $method = "_export_delete_$table";
303   $self->$method($svc_x, @_);
304 }
305
306 sub _export_delete_svc_acct {
307   my( $self, $svc_acct ) = (shift, shift);
308
309   $self->communigate_pro_queue( $svc_acct->svcnum, 'DeleteAccount',
310     $self->export_username($svc_acct),
311   );
312 }
313
314 sub _export_delete_svc_domain {
315   my( $self, $svc_domain ) = (shift, shift);
316
317   $self->communigate_pro_queue( $svc_domain->svcnum, 'DeleteDomain',
318     $svc_domain->domain,
319     #XXX turn on force option for domain deletion?
320   );
321 }
322
323 sub _export_delete_svc_forward {
324   my( $self, $svc_forward ) = (shift, shift);
325
326   $self->communigate_pro_queue( $svc_forward->svcnum, 'DeleteForwarder',
327     ($svc_forward->src || $svc_forward->srcsvc_acct->email),
328   );
329 }
330
331 sub _export_suspend {
332   my( $self, $svc_x ) = (shift, shift);
333
334   my $table = $svc_x->table;
335   my $method = "_export_suspend_$table";
336   $self->$method($svc_x, @_);
337
338 }
339
340 sub _export_suspend_svc_acct {
341   my( $self, $svc_acct ) = (shift, shift);
342
343   #XXX is this the desired suspnsion action?
344
345    $self->communigate_pro_queue(
346     $svc_acct->svcnum,
347     'UpdateAccountSettings',
348     $self->export_username($svc_acct),
349     'AccessModes' => 'Mail',
350   );
351
352 }
353
354 sub _export_suspend_svc_domain {
355   my( $self, $svc_domain) = (shift, shift);
356
357   #XXX domain operations
358   '';
359
360 }
361
362 sub _export_unsuspend {
363   my( $self, $svc_x ) = (shift, shift);
364
365   my $table = $svc_x->table;
366   my $method = "_export_unsuspend_$table";
367   $self->$method($svc_x, @_);
368
369 }
370
371 sub _export_unsuspend_svc_acct {
372   my( $self, $svc_acct ) = (shift, shift);
373
374   $self->communigate_pro_queue(
375     $svc_acct->svcnum,
376     'UpdateAccountSettings',
377     $self->export_username($svc_acct),
378     'AccessModes' => ( $svc_acct->cgp_accessmodes
379                          || $self->option('AccessModes') ),
380   );
381
382 }
383
384 sub _export_unsuspend_svc_domain {
385   my( $self, $svc_domain) = (shift, shift);
386
387   #XXX domain operations
388   '';
389
390 }
391
392
393 sub export_getsettings {
394   my($self, $svc_x) = (shift, shift);
395
396   my $table = $svc_x->table;
397   my $method = "export_getsettings_$table";
398
399   $self->can($method) ? $self->$method($svc_x, @_) : '';
400
401 }
402
403 sub export_getsettings_svc_domain {
404   my($self, $svc_domain, $settingsref, $defaultref ) = @_;
405
406   my $settings = eval { $self->communigate_pro_runcommand(
407     'GetDomainSettings',
408     $svc_domain->domain
409   ) };
410   return $@ if $@;
411
412   my $effective_settings = eval { $self->communigate_pro_runcommand(
413     'GetDomainEffectiveSettings',
414     $svc_domain->domain
415   ) };
416   return $@ if $@;
417
418   my $acct_defaults = eval { $self->communigate_pro_runcommand(
419     'GetAccountDefaults',
420     $svc_domain->domain
421   ) };
422   return $@ if $@;
423
424   #warn Dumper($acct_defaults);
425
426   %$effective_settings = ( %$effective_settings,
427                            map { ("Acct. Default $_" => $acct_defaults->{$_}); }
428                                keys(%$acct_defaults)
429                          );
430
431   #false laziness w/below
432   
433   my %defaults = map { $_ => 1 }
434                    grep !exists(${$settings}{$_}), keys %$effective_settings;
435
436   foreach my $key ( grep ref($effective_settings->{$_}),
437                     keys %$effective_settings )
438   {
439     my $value = $effective_settings->{$key};
440     if ( ref($value) eq 'ARRAY' ) {
441       $effective_settings->{$key} = join(' ', @$value);
442     } else {
443       #XXX
444       warn "serializing ". ref($value). " for table display not yet handled";
445     }
446   }
447
448   %{$settingsref} = %$effective_settings;
449   %{$defaultref} = %defaults;
450
451   '';
452 }
453
454 sub export_getsettings_svc_acct {
455   my($self, $svc_acct, $settingsref, $defaultref ) = @_;
456
457   my $settings = eval { $self->communigate_pro_runcommand(
458     'GetAccountSettings',
459     $svc_acct->email
460   ) };
461   return $@ if $@;
462
463   delete($settings->{'Password'});
464
465   my $effective_settings = eval { $self->communigate_pro_runcommand(
466     'GetAccountEffectiveSettings',
467     $svc_acct->email
468   ) };
469   return $@ if $@;
470
471   delete($effective_settings->{'Password'});
472
473   #prefs/effectiveprefs too
474
475   my $prefs = eval { $self->communigate_pro_runcommand(
476     'GetAccountPrefs',
477     $svc_acct->email
478   ) };
479   return $@ if $@;
480
481   my $effective_prefs = eval { $self->communigate_pro_runcommand(
482     'GetAccountEffectivePrefs',
483     $svc_acct->email
484   ) };
485   return $@ if $@;
486
487   %$effective_settings = ( %$effective_settings,
488                            map { ("Pref $_" => $effective_prefs->{$_}); }
489                                keys(%$effective_prefs)
490                          );
491   %$settings = ( %$settings,
492                  map { ("Pref $_" => $prefs->{$_}); }
493                      keys(%$prefs)
494                );
495
496   #aliases too
497
498   my $aliases = eval { $self->communigate_pro_runcommand(
499     'GetAccountAliases',
500     $svc_acct->email
501   ) };
502   return $@ if $@;
503
504   $effective_settings->{'Aliases'} = join(', ', @$aliases);
505   $settings->{'Aliases'}           = join(', ', @$aliases);
506
507   #false laziness w/above
508
509   my %defaults = map { $_ => 1 }
510                    grep !exists(${$settings}{$_}), keys %$effective_settings;
511
512   foreach my $key ( grep ref($effective_settings->{$_}),
513                     keys %$effective_settings )
514   {
515     my $value = $effective_settings->{$key};
516     if ( ref($value) eq 'ARRAY' ) {
517       $effective_settings->{$key} = join(' ', @$value);
518     } else {
519       #XXX
520       warn "serializing ". ref($value). " for table display not yet handled";
521     }
522   }
523
524   %{$settingsref} = %$effective_settings;
525   %{$defaultref} = %defaults;
526
527   '';
528
529 }
530
531 sub communigate_pro_queue {
532   my( $self, $svcnum, $method ) = (shift, shift, shift);
533   my $jobnum = ''; #don't actually care
534   $self->communigate_pro_queue_dep( \$jobnum, $svcnum, $method, @_);
535 }
536
537 sub communigate_pro_queue_dep {
538   my( $self, $jobnumref, $svcnum, $method ) = splice(@_,0,4);
539
540   my %kludge_methods = (
541     'CreateAccount'         => 'CreateAccount',
542     'UpdateAccountSettings' => 'UpdateAccountSettings',
543     'UpdateAccountPrefs'    => 'cp_Scalar_Hash',
544     'CreateDomain'          => 'cp_Scalar_Hash',
545     'CreateSharedDomain'    => 'cp_Scalar_Hash',
546     'UpdateDomainSettings'  => 'UpdateDomainSettings',
547   );
548   my $sub = exists($kludge_methods{$method})
549               ? $kludge_methods{$method}
550               : 'communigate_pro_command';
551
552   my $queue = new FS::queue {
553     'svcnum' => $svcnum,
554     'job'    => "FS::part_export::communigate_pro::$sub",
555   };
556   my $error = $queue->insert(
557     $self->machine,
558     $self->option('port'),
559     $self->option('login'),
560     $self->option('password'),
561     $method,
562     @_,
563   );
564   $$jobnumref = $queue->jobnum unless $error;
565
566   return $error;
567 }
568
569 sub communigate_pro_runcommand {
570   my( $self, $method ) = (shift, shift);
571
572   communigate_pro_command(
573     $self->machine,
574     $self->option('port'),
575     $self->option('login'),
576     $self->option('password'),
577     $method,
578     @_,
579   );
580
581 }
582
583 #XXX one sub per arg prototype is lame.  more magic?  i suppose queue needs
584 # to store data strctures properly instead of just an arg list.  right.
585
586 sub cp_Scalar_Hash {
587   my( $machine, $port, $login, $password, $method, $scalar, %hash ) = @_;
588   my @args = ( $scalar, \%hash );
589   communigate_pro_command( $machine, $port, $login, $password, $method, @args );
590 }
591
592 #sub cp_Hash {
593 #  my( $machine, $port, $login, $password, $method, %hash ) = @_;
594 #  my @args = ( \%hash );
595 #  communigate_pro_command( $machine, $port, $login, $password, $method, @args );
596 #}
597
598 sub UpdateDomainSettings {
599   my( $machine, $port, $login, $password, $method, $domain, %settings ) = @_;
600   my @args = ( 'domain' => $domain, 'settings' => \%settings );
601   communigate_pro_command( $machine, $port, $login, $password, $method, @args );
602 }
603
604 sub CreateAccount {
605   my( $machine, $port, $login, $password, $method, %args ) = @_;
606   my $accountName  = delete $args{'accountName'};
607   my $accountType  = delete $args{'accountType'};
608   my $externalFlag = delete $args{'externalFlag'};
609   $args{'AccessModes'} = [ split(' ', $args{'AccessModes'}) ];
610   my @args = ( accountName  => $accountName,
611                accountType  => $accountType,
612                settings     => \%args,
613              );
614                #externalFlag => $externalFlag,
615   push @args, externalFlag => $externalFlag if $externalFlag;
616
617   communigate_pro_command( $machine, $port, $login, $password, $method, @args );
618
619 }
620
621 sub UpdateAccountSettings {
622   my( $machine, $port, $login, $password, $method, $accountName, %args ) = @_;
623   $args{'AccessModes'} = [ split(' ', $args{'AccessModes'}) ];
624   my @args = ( $accountName, \%args );
625   communigate_pro_command( $machine, $port, $login, $password, $method, @args );
626 }
627
628 sub communigate_pro_command { #subroutine, not method
629   my( $machine, $port, $login, $password, $method, @args ) = @_;
630
631   eval "use CGP::CLI";
632
633   my $cli = new CGP::CLI( {
634     'PeerAddr' => $machine,
635     'PeerPort' => $port,
636     'login'    => $login,
637     'password' => $password,
638   } ) or die "Can't login to CGPro: $CGP::ERR_STRING\n";
639
640   #warn "$method ". Dumper(@args) if $DEBUG;
641
642   my $return = $cli->$method(@args)
643     or die "Communigate Pro error: ". $cli->getErrMessage. "\n";
644
645   $cli->Logout; # or die "Can't logout of CGPro: $CGP::ERR_STRING\n";
646
647   $return;
648
649 }
650
651 1;
652