A PHP wrapper to assist in interactions with the ISPConfig 3 web service offering. Also a list of patches to add features necessary for tasks I was attempting to complete. Each addition has been submitted to ISPConfig 3 for inclusion.
<?php /** * @author Ben Lake <me@benlake.org> * @license GNU Lesser Public License v3 (http://opensource.org/licenses/lgpl-3.0.html) * @copyright Copyright (c) 2011, Ben Lake * @link http://benlake.org/projects/show/ispconfigclient * * This file is part of the ISPConfig PHP Client. * * ISPConfig PHP Client is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ISPConfig PHP Client is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with ISPConfig PHP Client. If not, see <http://www.gnu.org/licenses/>. * * * This software is is NO WAY affiliated with ISPConfig. The license of this client does * not extend to ISPConfig itself! * ISPConfig is Copyright (c) 2007, Till Brehm, projektfarm Gmbh, All rights reserved. * You can find the source at http://www.ispconfig.org/ispconfig-3/ */ /** * Remote API client for ISPConfig3 to support logical operations of Lollipop. * Instantiate one object per server you wish to make adjustments to. Do not connect to server1 * to modify server2 (at least for now as there is no way to specify your server id explicitly). * MUST be able to connect over SSL! * * @package ispconfig3 * @version 0.1 */ class IspcfgClient { protected $host; protected $server_id; protected $soap; protected $client_username; protected $client_id; protected $session_id; /** * @param string the hostname of the ISPConfig3 server * @param string the client username this client will manipulate * @param int [8080] the port number * @param string [remote/index.php] the path of the web services endpoint */ function __construct($host, $client_username, $port = '8080', $path = 'remote/index.php') { 'location' => 'https://'.$host.':'.$port.'/'.$path, 'uri' => 'http://'.$host.'/remote/', 'exceptions' => true, // 'trace' => true, ); // create soap client in non-wsdl mode $this->soap = new SoapClient(null, $opts); $this->client_username = $client_username; $this->host = $host; } /** * Login to the remote interface (also fetches the server id and client id needed * by all other commands) * @param string the username to connect as * @param string the password of said user * @throws IspcfgCannotConnectException * @throws IspcfgAuthFailed * @throws IspcfgInvalidClient */ public function login($user, $pass) { try { } catch (SoapFault $e) { throw new IspcfgAuthFailed($e->getMessage(), 403); else throw new IspcfgCannotConnectException($e->getMessage(), 0); } // grab the client id necessary for any other command $this->client_id = $this->fetchClientId($this->client_username); $this->server_id = $this->fetchServerId($this->host); } /** * Adds a subdomain to one of the clients domains. Does the following: * 1. Adds a A record for <name>.tld * 2. Adds an MX record for <name>.tld to mail.tld * TODO maybe some basic validation on the TLD and IP? * @param string the top level domain name (eg. blah.com) * @param string the name of the subdomain (eg. mail) * @param string the IP address for this subdomain to resolve to * @return boolean * @throws IspcfgException * @throws IspcfgNoSessionException */ public function addSubdomain($tld, $name, $ip) { $this->checkSession(); $zone_id = $this->fetchZoneId($tld); // add the A record 'server_id' => $this->server_id, 'zone' => $zone_id, 'type' => 'A', 'name' => $name, 'data' => $ip, 'ttl' => '86400', 'active' => 'y', ); try { } catch (SoapFault $e) { throw new IspcfgException($e->getMessage()); } // add the MX record 'server_id' => $this->server_id, 'zone' => $zone_id, 'type' => 'MX', 'name' => $name.'.'.$tld.'.', 'data' => 'mail.'.$tld.'.', 'ttl' => '86400', 'aux' => '10', 'active' => 'y', ); try { } catch (SoapFault $e) { throw new IspcfgException($e->getMessage()); } return true; } /** * Setup mail for a domain and setup default SPAM policy. * @param string the domain to add * @return boolean * @throws IspcfgException * @throws IspcfgNoSessionException */ public function addMailDomain($domain) { $this->checkSession(); // Add domain to mailserver 'server_id' => $this->server_id, 'domain' => $domain, 'active' => 'y', ); try { } catch (SoapFault $e) { throw new IspcfgException($e->getMessage()); } // Add spam policy for domain 'server_id' => $this->server_id, 'priority' => 5, 'policy_id' => 5, // Normal 'email' => '@'.$domain, 'fullname' => '@'.$domain, 'local' => 'Y', ); try { } catch (SoapFault $e) { throw new IspcfgException($e->getMessage()); } return true; } /** * Add a new mail account to domain with SPAM policy * @param string the domain the email account is being added to * @param string the name (that which precedes @) of the mail account * @param string the password for the mail account * @param integer [50] number in MB of mail quota * @param string [normal] the spam policy to set for the mail account * @return boolean * @throws IspcfgException * @throws IspcfgNoSessionException */ public function addMailAccount($domain, $mailbox, $passwd, $quota = 50, $spam = 'normal') { $this->checkSession(); switch ($spam) { default: $spam = 5; break; } // Add mail user 'server_id' => $this->server_id, 'email' => $mailbox.'@'.$domain, 'name' => $mailbox, 'password' => $passwd, 'postfix' => 'y', // enable receiving 'homedir' => '/var/vmail', 'maildir' => '/var/vmail/'.$domain.'/'.$mailbox, 'uid' => 5000, 'gid' => 5000, ); try { } catch (SoapFault $e) { throw new IspcfgException($e->getMessage()); } // Add mail user's spam policy 'server_id' => $this->server_id, 'priority' => 10, 'policy_id' => $spam, // Normal 'email' => $mailbox.'@'.$domain, 'fullname' => $mailbox, 'local' => 'Y', ); try { } catch (SoapFault $e) { throw new IspcfgException($e->getMessage()); } return true; } /** * Remove a top-level domain and associated records from DNS. * @param string the fully qualified domain name (or subdomain) * @return boolean true on success * @throws IspcfgInvalidDomainException if the domain does not exist * @throws IspcfgException * @throws IspcfgNoSessionException */ public function removeDomain($tld, $name) { $this->checkSession(); try { throw new IspcfgInvalidDomainException('Domain '.$domain.' was not found'); } catch (SoapFault $e) { throw new IspcfgException($e->getMessage()); } return true; } /** * Remove a subdomain and associated records from DNS. * Removes a subdomain to from a top level domain and any A, CNAME, MX records that are related. * TODO maybe some basic validation on the TLD and IP? * @param string the top level domain name (eg. blah.com) * @param string the name of the subdomain (eg. mail) * @throws IspcfgInvalidDomainException if the domain does not exist * @throws IspcfgException * @throws IspcfgNoSessionException */ public function removeSubdomain($tld, $name) { $this->checkSession(); try { } catch (SoapFault $e) { throw new IspcfgException($e->getMessage()); } } /** * Remove a mail domain, all associated users, and any spam settings for the domain or users. * @param string the fully qualified domain name (or subdomain) * @return boolean true on success * @throws IspcfgInvalidDomainException if the domain does not exist * @throws IspcfgException * @throws IspcfgNoSessionException */ public function removeMailDomain($domain) { $this->checkSession(); try { throw new IspcfgInvalidDomainException('Domain '.$domain.' was not found'); $domain_id = $r[0]['domain_id']; $this->removeMailAccounts($domain); } catch (SoapFault $e) { throw new IspcfgException($e->getMessage()); } return true; } /** * Remove all mail accounts for a mail domain. * @param string the fully qualified domain name (or subdomain) * @param array optional list of mail accounts to delete, all are delete if not specified (<user>@domain.com) * @return boolean false if no users were found, true if any were found and successfully removed * @throws IspcfgException * @throws IspcfgNoSessionException */ { $this->checkSession(); // affix the domain to each specified user to make comparison tolerable { foreach ($only as $k => $u) $only[$k] = $u.'@'.$domain; } try { // TODO should probably add a paramter or method to only bring back specified users return false; foreach ($users as $r) { continue; } } catch (SoapFault $e) { throw new IspcfgException($e->getMessage()); } return true; } /** * Logout of the remote interface */ public function logout() { $this->checkSession(); try { } catch (SoapFault $e) { throw new IspcfgException($e->getMessage(), 500); } } /** * Logout if the object is destroyed and logout was not called. */ public function __destruct() { $this->logout(); } // ============ // = internal = // ============ /** * Retrieve the server id from the specified IP address. * @param string the server's hostname or IP address * @return string * @throws IspcfgException * @throws IspcfgUnknownServerException */ protected function fetchServerId($host) { // TODO make IPv6 aware else $ip = $host; throw new IspcfgException('Unable to determine IP for host '.$host); try { throw new IspcfgUnknownServerException('No server found with IP '.$ip); $server_id = $server_id['server_id']; } catch (SoapFault $e) { throw new IspcfgException($e->getMessage(), 500); } return $server_id; } /** * Retrieve the client id from the provided username. This value is necessary for nearly * every other commands. * @param string the client username * @return integer * @throws IspcfgInvalidClientException if the client is not found */ protected function fetchClientId($username) { try { $client_id = $this->soap->client_get_by_username( $username ); throw new IspcfgInvalidClientException('Client username not found'); $client_id = $client_id['client_id']; } catch (SoapFault $e) { throw new IspcfgException($e->getMessage(), 500); } return $client_id; } /** * Retrieve the zone id for a domain which is an internal identifier needed when * calling dns related commands. * TODO cache zone id lookups performed while this object is alive * @param string the domain name to fetch the zone id for * @return integer * @throws IspcfgInvalidDomainException * @throws IspcfgException */ protected function fetchZoneId($root_tld) { // the value returned will have a trailing dot so add for comparison $root_tld .= '.'; try { $zones = $this->soap->dns_zone_get_by_user( $this->client_id, $this->server_id ); { foreach ($zones as $z) { if ($z['origin'] == $root_tld) { $zone_id = $z['id']; break; } } } else { throw new IspcfgInvalidDomainException('The domain '.$root_tld.' is not valid for this client'); } } catch (SoapFault $e) { throw new IspcfgException($e->getMessage(), 500); } return $zone_id; } /** * Verify we've logged into the remote interface successfully. * @throws IspcfgNoSessionException */ protected function checkSession() { throw new IspcfgNoSessionException('Please login first!'); } } // END Ispcfg3Client // ============== // = Exceptions = // ============== class IspcfgException extends Exception {} class IspcfgUnknownServerException extends Exception {} /** * Thrown when calling a command without first calling login. */ class IspcfgNoSessionException extends Exception {} class IspcfgInvalidClientException extends Exception {} class IspcfgInvalidDomainException extends Exception {} class IspcfgCannotConnectException extends Exception {} class IspcfgAuthFailed extends Exception {}
Save-As: plain/text
Patch accepted! See revision 2331
--- /usr/local/ispconfig/interface/lib/classes/remoting.inc.php 2011-04-20 11:58:02.150102228 -0500 +++ ispconfig3_install/interface/lib/classes/remoting.inc.php 2011-04-20 12:08:02.618061519 -0500 @@ -2395,24 +2395,29 @@ return false; } } - - public function mail_domain_get_by_domain($session_id, $domain) { + + /** + * Fetch the mail_domain record for the provided domain. + * @param int session_id + * @param string the fully qualified domain (or subdomain) + * @return array array of arrays corresponding to the mail_domain table's records + * @author Unknown + */ + public function mail_domain_get_by_domain($session_id, $domain) { global $app; if(!$this->checkPerm($session_id, 'mail_domain_get_by_domain')) { $this->server->fault('permission_denied', 'You do not have the permissions to access this function.'); return false; - } - if (!empty($domain_id)) { - $domain = $app->db->quote($domain); - $sql = "SELECT * FROM mail_domain WHERE domain = $domain"; + } + if (!empty($domain)) { + $domain = $app->db->quote($domain); + $sql = "SELECT * FROM mail_domain WHERE domain = '$domain'"; $result = $app->db->queryAllRecords($sql); return $result; } return false; } - - - + /** * Get a list of functions * @param int session id @@ -2534,4 +2539,4 @@ } } } -?> \ No newline at end of file +?>
Save-As: plain/text
--- /usr/local/ispconfig/interface/lib/classes/remoting.inc.php 2011-04-20 10:38:28.574075332 -0500 +++ ispconfig3_install/interface/lib/classes/remoting.inc.php 2011-04-20 11:14:08.730841387 -0500 @@ -2397,6 +2397,43 @@ } /** + * Retrieve all mail accounts for the specified domain or domain id. + * @param int session_id + * @param string [null] the fully qualified domain (or subdomain), null to ignore + * @param int [null] the domain id, null to ignore, takes precedence if both are provided + * @return array array of arrays representing mail_user records or an empty array if no results + * @author Ben Lake <dev@benlake.org> + */ + public function mail_domain_get_users($session_id, $domain = null, $domain_id = null) + { + global $app; + + if (empty($domain) && empty($domain_id)) { + $this->server->fault('invalid_arguments', 'You must specify either a domain name or domain id, neither provided'); + } + + if(!$this->checkPerm($session_id, 'mail_domain_get_users')) { + $this->server->fault('permission_denied', 'You do not have the permissions to access this function.'); + return false; + } + + if (!empty($domain_id)) { + $domain_id = intval($domain_id); + $sql = "SELECT * FROM mail_user WHERE email LIKE concat('%@', (SELECT domain FROM mail_domain WHERE domain_id = ".$domain_id."))"; + } + elseif (!empty($domain)) { + $domain = $app->db->quote($domain); + $sql = "SELECT * FROM mail_user WHERE email LIKE '%@".$domain."'"; + } + + $result = $app->db->queryAllRecords($sql); + if (!is_array($result)) + $result = array(); + + return $result; + } + + /** * Fetch the mail_domain record for the provided domain. * @param int session_id * @param string the fully qualified domain (or subdomain)
Save-As: plain/text
--- /usr/local/ispconfig/interface/lib/classes/remoting.inc.php 2011-04-20 12:31:46.906037774 -0500 +++ ispconfig3_install/interface/lib/classes/remoting.inc.php 2011-04-20 12:52:37.786813579 -0500 @@ -652,7 +652,29 @@ $affected_rows = $this->deleteQuery('../mail/form/spamfilter_users.tform.php', $primary_id); return $affected_rows; } - + + /** + * Remove a spamfilter user using the user's email address. + * @param int session_id + * @param string email address or (@tld is ok too, but this is not a wildcard or anything) + * @return int the number of removed policies + * @author Unknown + */ + public function mail_spamfilter_user_delete_by_email($session_id, $email) + { + global $app; + if (!$this->checkPerm($session_id, 'mail_spamfilter_user_delete')) { + $this->server->fault('permission_denied','You do not have the permissions to access this function.'); + return false; + } + + $email = $app->db->quote($email); + $sql = "DELETE FROM spamfilter_users WHERE email = '".$email."'"; + $app->db->query($sql); + + return $app->db->affectedRows(); + } + //* Get policy details public function mail_policy_get($session_id, $primary_id) {
Save-As: plain/text
--- /usr/local/ispconfig/interface/lib/classes/remoting.inc.php 2011-04-20 14:05:35.064084960 -0500 +++ ispconfig3_install/interface/lib/classes/remoting.inc.php 2011-04-20 15:06:35.519360973 -0500 @@ -2575,7 +2575,55 @@ return false; } } - + + /** + * Remove a DNS zone and all associated records. + * @param int session_id + * @param string [null] the fully qualified domain (or subdomain), null to ignore + * @param int [null] the domain id, null to ignore, takes precedence if both are provided + * @return boolean true upon success, false if the dns_soa record is not found + * @author Ben Lake <dev@benlake.org> + */ + public function dns_delete_all($session_id, $domain = null, $domain_id = null) { + global $app; + + if (empty($domain) && empty($domain_id)) { + $this->server->fault('invalid_arguments', 'You must specify either a domain name or domain id, neither provided'); + } + + if(!$this->checkPerm($session_id, 'dns_zone_delete')) { + $this->server->fault('permission_denied', 'You do not have the permissions to access this function.'); + return false; + } + + // affix a dot to the domain as that is how they are stored + $domain .= '.'; + + // look up the domain id needed for removing dns_rr records + if (empty($domain_id)) { + $domain = $app->db->quote($domain); + $sql = "SELECT id FROM dns_soa WHERE origin = '".$domain."' LIMIT 1"; + $r = $app->db->queryOneRecord($sql); + if (empty($r)) return false; + $domain_id = $r['id']; + } + + $domain_id = intval($domain_id); + if ($domain_id == 0) return false; + + // remove the SOA record + $sql = "DELETE FROM dns_soa WHERE id = ".$domain_id; + $app->db->query($sql); + + // if we had a hit, go for the dns_rr + if ($app->db->affectedRows() > 0) { + $sql = "DELETE FROM dns_rr WHERE zone = ".$domain_id; + $app->db->query($sql); + } + + return true; + } + public function mail_domain_set_status($session_id, $primary_id, $status) { global $app; if(!$this->checkPerm($session_id, 'mail_domain_set_status')) {
Save-As: plain/text
--- /usr/local/ispconfig/interface/lib/classes/remoting.inc.php 2011-04-20 17:08:31.374041468 -0500 +++ ispconfig3_install/interface/lib/classes/remoting.inc.php 2011-04-20 17:50:54.818059432 -0500 @@ -2624,6 +2624,56 @@ return true; } + /** + * Attempts to remove all records associated with the provided subdomain. That means all + * record types are affected. + * + * Example for zone google.com: + * mail A x.x.x.x + * google.com MX mail.google.com + * + * Performing dns_delete_subdomain($sess, 'google.com','mail') would remove both records + * becuase both rely on the subdomain mail.google.com. So any record that utilizes 'mail' + * or 'mail.google.com' will be removed. + * + * @param int session_id + * @param string top level domain (e.g. google.com) + * @param int the subdomain component (e.g. mail, or one.mail, etc.) + * @return boolean true upon success, false if no records were found + * @author Ben Lake <dev@benlake.org> + */ + public function dns_delete_subdomain($session_id, $tld, $subname) { + global $app; + + if(!$this->checkPerm($session_id, 'dns_zone_delete')) { + $this->server->fault('permission_denied', 'You do not have the permissions to access this function.'); + return false; + } + + // affix a dot to the domain as that is how they are stored + $domain = $tld.'.'; + + // look up the domain id needed for removing dns_rr records + $domain = $app->db->quote($domain); + $sql = "SELECT id FROM dns_soa WHERE origin = '".$domain."' LIMIT 1"; + $r = $app->db->queryOneRecord($sql); + if (empty($r)) return false; + $domain_id = intval($r['id']); + + $domain = $subname.'.'.$tld.'.'; + + $subname = $app->db->quote($subname); + $domain = $app->db->quote($domain); + $sql = "DELETE FROM dns_rr WHERE name = '".$subname."' OR name = '".$domain + ."' OR data = '".$subname."' OR data = '".$domain."' AND zone = ".$domain_id; + $app->db->query($sql); + + if ($app->db->affectedRows() == 0) + return false; + + return true; + } + public function mail_domain_set_status($session_id, $primary_id, $status) { global $app; if(!$this->checkPerm($session_id, 'mail_domain_set_status')) {
Save-As: plain/text