1 package FS::part_export::ispconfig3;
5 use base qw( FS::part_export );
14 FS::part_export::ispconfig3
18 ISPConfig 3 integration for Freeside
22 This export offers basic svc_acct provisioning for ISPConfig 3.
23 All email accounts will be assigned to a single specified client.
25 This module also provides generic methods for working through the L</ISPConfig3 API>.
33 option_labels => { 'y' => 'yes', 'n' => 'no' },
36 tie my %options, 'Tie::IxHash',
37 'soap_location' => { label => 'SOAP Location' },
38 'username' => { label => 'User Name',
40 'password' => { label => 'Password',
42 'debug' => { type => 'checkbox',
43 label => 'Enable debug warnings' },
44 'subheading' => { type => 'title',
45 label => 'Account defaults' },
46 'client_id' => { label => 'Client ID' },
47 'server_id' => { label => 'Server ID' },
48 'maildir' => { label => 'Maildir (substitutions from svc_acct, e.g. /mail/$domain/$username)', },
49 'cc' => { label => 'Cc' },
50 'autoresponder_text' => { label => 'Autoresponder text',
51 default => 'Out of Office Reply' },
52 'move_junk' => { type => 'select',
54 option_labels => { 'y' => 'yes', 'n' => 'no' },
55 label => 'Move junk' },
56 'postfix' => { type => 'select',
59 'access' => { type => 'select',
62 'disableimap' => { type => 'select',
64 label => 'Disable IMAP' },
65 'disablepop3' => { type => 'select',
67 label => 'Disable POP3' },
68 'disabledeliver' => { type => 'select',
70 label => 'Disable deliver' },
71 'disablesmtp' => { type => 'select',
73 label => 'Disable SMTP' },
78 'desc' => 'Export email account to ISPConfig 3',
79 'options' => \%options,
82 All email accounts will be assigned to a single specified client and server.
86 sub _mail_user_params {
87 my ($self, $svc_acct) = @_;
88 # all available api fields are in comments below, even if we don't use them
91 'server_id' => $self->option('server_id'),
93 'email' => $svc_acct->username.'@'.$svc_acct->domain,
95 'login' => $svc_acct->username.'@'.$svc_acct->domain,
96 #password (varchar(255))
97 'password' => $svc_acct->_password,
99 'name' => $svc_acct->finger,
101 'uid' => $svc_acct->uid,
103 'gid' => $svc_acct->gid,
104 #maildir (varchar(255))
105 'maildir' => $self->_substitute($self->option('maildir'),$svc_acct),
107 'quota' => $svc_acct->quota,
109 'cc' => $self->option('cc'),
110 #homedir (varchar(255))
111 'homedir' => $svc_acct->dir,
113 ## initializing with autoresponder off, but this could become an export option...
114 #autoresponder (enum('n','y'))
115 'autoresponder' => 'n',
116 #autoresponder_start_date (datetime)
117 #autoresponder_end_date (datetime)
118 #autoresponder_text (mediumtext)
119 'autoresponder_text' => $self->option('autoresponder_text'),
121 #move_junk (enum('n','y'))
122 'move_junk' => $self->option('move_junk'),
123 #postfix (enum('n','y'))
124 'postfix' => $self->option('postfix'),
125 #access (enum('n','y'))
126 'access' => $self->option('access'),
128 ## not needed right now, not sure what it is
129 #custom_mailfilter (mediumtext)
131 #disableimap (enum('n','y'))
132 'disableimap' => $self->option('disableimap'),
133 #disablepop3 (enum('n','y'))
134 'disablepop3' => $self->option('disablepop3'),
135 #disabledeliver (enum('n','y'))
136 'disabledeliver' => $self->option('disabledeliver'),
137 #disablesmtp (enum('n','y'))
138 'disablesmtp' => $self->option('disablesmtp'),
143 my ($self, $svc_acct) = @_;
144 return $self->api_error || 'Error logging in'
145 unless $self->api_login;
146 my $params = $self->_mail_user_params($svc_acct);
147 my $remoteid = $self->api_call('mail_user_add',$self->option('client_id'),$params);
148 return $self->api_error_logout if $self->api_error;
149 my $error = $self->set_remoteid($svc_acct,$remoteid);
150 $error = "Remote system updated, but error setting remoteid ($remoteid): $error"
156 sub _export_replace {
157 my ($self, $svc_acct, $svc_acct_old) = @_;
158 return $self->api_error || 'Error logging in'
159 unless $self->api_login;
160 my $remoteid = $self->get_remoteid($svc_acct_old);
161 return "Could not load remoteid for old service" unless $remoteid;
162 my $params = $self->_mail_user_params($svc_acct);
163 #API docs claim "Returns the number of affected rows"
164 my $success = $self->api_call('mail_user_update',$self->option('client_id'),$remoteid,$params);
165 return $self->api_error_logout if $self->api_error;
166 return "Server returned no rows updated, but no other error message" unless $success;
168 unless ($svc_acct->svcnum eq $svc_acct_old->svcnum) { # are these ever not equal?
169 $error = $self->set_remoteid($svc_acct,$remoteid);
170 $error = "Remote system updated, but error setting remoteid ($remoteid): $error"
178 my ($self, $svc_acct) = @_;
179 return $self->api_error || 'Error logging in'
180 unless $self->api_login;
181 my $remoteid = $self->get_remoteid($svc_acct);
182 #don't abort deletion--
183 # might have been provisioned before export was implemented,
184 # still need to be able to delete from freeside
186 warn "Could not load remoteid for svcnum ".$svc_acct->svcnum.", unprovisioning anyway";
189 #API docs claim "Returns the number of deleted records"
190 my $success = $self->api_call('mail_user_delete',$remoteid);
191 return $self->api_error_logout if $self->api_error;
192 #don't abort deletion--
193 # if it's already been deleted remotely,
194 # still need to be able to delete from freeside
195 warn "Server returned no records deleted for svcnum ".$svc_acct->svcnum.
196 " remoteid $remoteid, unprovisioning anyway"
202 sub _export_suspend {
203 my ($self, $svc_acct) = @_;
207 sub _export_unsuspend {
208 my ($self, $svc_acct) = @_;
212 =head1 ISPConfig3 API
214 These methods allow access to the ISPConfig3 API using the credentials
215 set in the export options.
221 Accepts I<$method> and I<@params>. Places an api call to the specified
222 method with the specified params. Returns the result of that call
223 (empty on failure.) Retrieve error messages using L</api_error>.
225 Do not include session id in list of params; it will be included
226 automatically. Must run L</api_login> first.
231 my ($self,$method,@params) = @_;
232 # This does get used by api_login,
233 # to retrieve the session id after it sets the client,
234 # so we only check for existence of client,
235 # and we only include session id if we have one
236 my $client = $self->{'__ispconfig_client'};
238 $self->{'__ispconfig_error'} = 'Not logged in';
241 if ($self->{'__ispconfig_session'}) {
242 unshift(@params,$self->{'__ispconfig_session'});
244 # Contact server in eval, to trap connection errors
245 warn "Calling SOAP method $method with params:\n".Dumper(\@params)."\n"
246 if $self->option('debug');
247 my $response = eval { $client->$method(@params) };
249 $self->{'__ispconfig_error'} = "Error contacting server: $@";
252 # Set results and return
253 $self->{'__ispconfig_error'} = $response->fault
254 ? "Error from server: " . $response->faultstring
256 return $response->result;
261 Returns the error string set by L</ISPConfig3 API> methods,
262 or a blank string if most recent call produced no errors.
268 return $self->{'__ispconfig_error'} || '';
271 =head2 api_error_logout
273 Attempts L</api_logout>, but returns L</api_error> message from
274 before logout was attempted. Useful for logging out
275 properly after an error.
279 sub api_error_logout {
281 my $error = $self->api_error;
288 Initializes an api session using the credentials for this export.
289 Returns true on success, false on failure.
290 Retrieve error messages using L</api_error>.
296 if ($self->{'__ispconfig_session'} || $self->{'__ispconfig_client'}) {
297 $self->{'__ispconfig_error'} = 'Already logged in';
300 $self->{'__ispconfig_session'} = undef;
301 $self->{'__ispconfig_client'} =
302 SOAP::Lite->proxy($self->option('soap_location'), ssl_opts => [ verify_hostname => 0 ] )
304 unless ($self->{'__ispconfig_client'}) {
305 $self->{'__ispconfig_error'} = 'Error creating SOAP client';
308 $self->{'__ispconfig_session'} =
309 $self->api_call('login',$self->option('username'),$self->option('password'))
311 return unless $self->{'__ispconfig_session'};
317 Ends the current api session established by L</api_login>.
318 Returns true on success, false on failure.
324 unless ($self->{'__ispconfig_session'}) {
325 $self->{'__ispconfig_error'} = 'Not logged in';
328 my $result = $self->api_call('logout');
329 # clear these even if there was a failure to logout
330 $self->{'__ispconfig_client'} = undef;
331 $self->{'__ispconfig_session'} = undef;
332 return if $self->api_error;
336 # false laziness with portaone export
338 my ($self, $string, @objects) = @_;
339 return '' unless $string;
340 foreach my $object (@objects) {
342 my @fields = $object->fields;
343 push(@fields,'domain') if $object->table eq 'svc_acct';
344 foreach my $field (@fields) {
346 my $value = $object->$field;
347 $string =~ s/\$$field/$value/g;
350 # strip leading/trailing whitespace
363 jonathan@freeside.biz