Backup (DNS and Mail)

Discussion in 'Installation/Configuration' started by PDJ, Sep 16, 2020.

  1. PDJ

    PDJ Member

    I ran into a small problem when I want to add a slave zone (dns_slave_add) I did set all the parameters found in the file dns_slave.tform.php
    Code:
     'server_id' => 1,
     'origin' => $origin,
     'ns' => $ns,
     'xfer' => $xfer,
     'active' => 'Y'
    
    But I get an error: ns_error_regex
    I will search to code if I can find something that I did wrong, but maybe someone in here has more experience.
     
  2. Th0m

    Th0m ISPConfig Developer Staff Member ISPConfig Developer

    It means you used a hostname that's not allowed, maybe you can parse the values and see what went wrong.
     
  3. PDJ

    PDJ Member

    Sure, here they are
    [server_id] => 1
    [origin] => domain.com.
    [ns] => ns2.nameserver.com
    [xfer] => 10.178.34.110
    [active] => Y

    I changed the origin and ns, but in the same format
     
  4. till

    till Super Moderator Staff Member ISPConfig Developer

    The dot at the end of the ns string is missing. It must be "ns2.nameserver.com." and not "ns2.nameserver.com".
     
  5. PDJ

    PDJ Member

    Thank you for your reply.
    I'm sorry that didn't resolve the error
     
  6. PDJ

    PDJ Member

    I think I found the problem in dns_slave.tform.php for ns I see 'type' => 'ISIP' does this mean there can only be an IP not a domain?
     
  7. till

    till Super Moderator Staff Member ISPConfig Developer

    Yes, that#s possible. You can only use values in the API that you can use in the interface.
     
  8. Th0m

    Th0m ISPConfig Developer Staff Member ISPConfig Developer

    I saw that aswell, but on testing it I don't have a problem.
    Yes, it has to be a IP address for slave zones. The IP address should be the main name server.
     
  9. PDJ

    PDJ Member

    Thanks, it was indeed the IP issue.
    I have managed to complete the script, it works fine, master syncs the slave, so it does everything I expected so far.
    Are you still interested in the script?
     
  10. Th0m

    Th0m ISPConfig Developer Staff Member ISPConfig Developer

    Yes, go ahead and share it so if someones wants to use it they can :)
     
  11. PDJ

    PDJ Member

    Code:
    <?php
    /*
    Copyright (c) 2020 B. Wubben
    
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
    
    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
    
    Script for updating the Slave DNS zone records (create and remove)
    
    
    
    */
    // Config the Secondary DNS servers here where
    // array (ServerID (does not have to be the ispconfig ID,actually it's not used),host or IP,Port,loginname,loginpass,Check certificate)
    $secondaryservers=array(
        array(1,'','8080','','','N')
    );
    
    /* Soap Settings */
    // Local
    // Local username
    $username_local = 'localuser';
    // Local password
    $password_local = 'localpassword';
    // Local Soap location
    $soap_location_local = 'https://localhost:8080/remote/index.php';
    // Local Soap URI
    $soap_uri_local = 'https://localhost:8080/remote/';
    // Local connection timeout in seconds
    $soap_timeout_local = 4;
    // The IP where I comunnicate with the other Nameservers
    $my_notify_ip = 'myip';
    // Owner of the records
    $my_client_id = 1;
    
    // Remote
    // Remote connection timeout in seconds
    $soap_timeout_remote = 8;
    
    /* End Soap settings */
    
    // Functions
    function SoapConnect($location,$uri,$checkSSL,$timeout = 60) {
        try {
                if ($checkSSL != 'Y') {
                        $con = new SoapClient(null, array('location' => $location,
                                'uri'      => $uri,
                                   'trace' => 1,
                                'exceptions' => 1,
                                'connection_timeout'=> $timeout,
                                'stream_context'=> stream_context_create(array('ssl'=> array('verify_peer'=>false,'verify_peer_name'=>false)))
                                   ));
    
                } else {
                        $con = new SoapClient(null, array('location' => $location,
                                'uri'      => $uri,
                                'trace' => 1,
                                'exceptions' => 1,
                                'connection_timeout'=> $timeout
                                   ));
            }
    
        } catch (SoapFault $e) {
            $con = NULL;
        }
        return $con;
    }
    // End functions
    
    // First get the remote DNS record
    $errorserver=array();
    $dns_zone_remote=array();
    foreach ($secondaryservers as $secondaryserver) {
        if ($secondaryserver[2]!='') {
            $http_s='https://'.$secondaryserver[1].':'.$secondaryserver[2];
        } else {
            $http_s='https://'.$secondaryserver[1];
        }
        $client_remote = SoapConnect($http_s.'/remote/index.php',$http_s.'/remote/',$secondaryserver[5],$soap_timeout_remote);
        if (!is_null($client_remote)) {
            try {
                    if($session_id = $client_remote->login($secondaryserver[3],$secondaryserver[4])) {
                            echo ("Logged in successfull on ".$secondaryserver[1]." Session ID:".$session_id." \r\n");
                    }
    
                    // Get all the master zone list local
                    $dns_zones = $client_remote->dns_zone_get($session_id,-1);
                foreach ($dns_zones as $dns_zone) {
                    // Check if I'm the notify server otherwise it has no use to act as secondary server
                    if ((array_key_exists('xfer',$dns_zone)) && ($dns_zone['xfer'] == $my_notify_ip) && ($dns_zone['active'] == 'Y')) {
                        $dns_zone_remote[] = $dns_zone;
                    }
                }
                    // logout
                    if($client_remote->logout($session_id)) {
                            echo ("Remote logged out from ".$secondaryserver[1].". \r\n");
                    }
            } catch (SoapFault $e) {
                    echo $client_remote->__getLastResponse();
                    echo("SOAP Error (".$secondaryserver[1].") : ".$e->getMessage()."\r\n");
                // add this server to the errorlist
                $errorserver[]=$secondaryserver[1];
            }
        } else {
            // add this server to the errorlist
            $errorserver[]=$secondaryserver[1];
        }
    }
    
    // Make a local connection
    $client_local = SoapConnect($soap_location_local,$soap_uri_local,'N',$soap_timeout_local);
    // try to login
    try {
        if($session_id = $client_local->login($username_local, $password_local)) {
            echo ("Local logged in successfull Session ID:".$session_id.". \r\n");
        }
    
    
        // Get all the master zone list local
        $dns_slave_records = $client_local->dns_slave_get($session_id,-1);
    
        // Add all the zone records from remote to local slave, we don't have to check for dupication in the primary zone, ISPConfig will do that for us
        foreach ($dns_zone_remote as $dns_zone) {
            // Check if the zone is allready in the list
            if (array_search($dns_zone['origin'], array_column($dns_slave_records, 'origin')) === false) {
                // Check what server this record is from, so we know it's nameserver hostname
                $key = array_search($dns_zone['xfer'], array_column($secondaryservers,1));
                $ns = $secondaryservers[$key][1];
                echo ("NS : $ns \r\n");
                // Add this record to the slave zone
                echo ($dns_zone['origin']." not found, add it \r\n");
                $params = array(
                    'server_id' => 1,
                    'origin' => $dns_zone['origin'],
                    'ns' => $ns,
                    'xfer' => '',
                    'active' => 'Y'
                );
                $res = $client_local->dns_slave_add($session_id,$my_client_id,$params );
                if ($res == -1) {
                    echo ($dns_zone['origin']." add error $res \r\n");
                } else {
                    echo ($dns_zone['origin']." added to slave \r\n");
                }
            }
        }
    
        // Now delete if its not in the list
        foreach ($dns_slave_records as $dns_zone) {
            if (array_search($dns_zone['origin'], array_column($dns_zone_remote, 'origin')) === false) {
                // Check if the record is not from an error server (server down = don't delete)
                if (in_array($dns_zone['ns'],$errorserver) === false) {
                    // Delete the record
                    echo ("Delete zone ".$dns_zone['origin']."\r\n");
                    $res = $client_local->dns_slave_delete($session_id,$dns_zone['id']);
                                if ($res == -1) {
                                        echo ($dns_zone['origin']." delete error $res \r\n");
                                } else {
                                        echo ($dns_zone['origin']." deleted from slave \r\n");
                                }
                }
            }
        }
    
    
     
        // logout
        if($client_local->logout($session_id)) {
            echo ("Local logged out. \r\n");
        }
    } catch (SoapFault $e) {
        echo $client_local->__getLastResponse();
        die('SOAP Error: '.$e->getMessage());
    }
    echo ("DNS Update Done \r\n");
    ?>
    
    What needs to be configure is:
    the remote server(s) $secondaryservers you can add as many ISPConfig servers you want, the server the script is running on will be the backup of that server, the 'N' at the end stands for do not check certificate (https), 'Y' means certificate must be ok.
    $username_local and $password_local this needs to be the local soap login
    $my_notify_ip this is the IP where the primary name server is sending the updates to (this IP is used to match the records, if it's not set correctly the slave records will be deleted)
    $my_client_id this is the user id where the backup records are assigned to (I have made a server account that's suspended) and assigned the ID, so that nobody can delete the secondary records.
    optional
    $soap_timeout_local and $soap_timeout_remote, this is the timeout for the soap connection (note: this does not work correctly in PHP 5)

    Please let me know if you are going to use the script..
     
    Last edited: Oct 4, 2020
    TonyG and ahrasis like this.
  12. PDJ

    PDJ Member

    Forgot to mention, this script can be run from the crontab
     
  13. PDJ

    PDJ Member

    Ok, here is the script for mail back also, the configuration is almost the same, there is 1 extra setting.
    Code:
    <?php
    /*
    Copyright (c) 2020 B. Wubben
    
    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:
    
    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
    
    Script for updating the Slave DNS zone records (create and remove)
    
    
    
    */
    // Config the backup mail servers here where
    // array (ServerID (does not have to be the ispconfig ID, but must be unique),IP,Port,loginname,loginpass,Check certificate,SMTP forward IP/Host)
    $secondaryservers=array(
        array(1,'','8080','','','N','')
    );
    
    /* Soap Settings */
    // Local
    // Local username
    // Local username
    $username_local = 'localuser';
    // Local password
    $password_local = 'localpassword';
    // Local Soap location
    $soap_location_local = 'https://localhost:8080/remote/index.php';
    // Local Soap URI
    $soap_uri_local = 'https://localhost:8080/remote/';
    // Local connection timeout in seconds
    $soap_timeout_local = 4;
    // The IP where I comunnicate with the other Nameservers
    $my_notify_ip = 'myip';
    // Owner of the records
    $my_client_id = 4;
    // Remote
    // Remote connection timeout in seconds
    $soap_timeout_remote = 8;
    
    /* End Soap settings */
    
    // Functions
    function SoapConnect($location,$uri,$checkSSL,$timeout = 60) {
        try {
                if ($checkSSL != 'Y') {
                        $con = new SoapClient(null, array('location' => $location,
                                'uri'      => $uri,
                                   'trace' => 1,
                                'exceptions' => 1,
                                'connection_timeout'=> $timeout,
                                'stream_context'=> stream_context_create(array('ssl'=> array('verify_peer'=>false,'verify_peer_name'=>false)))
                                   ));
    
                } else {
                        $con = new SoapClient(null, array('location' => $location,
                                'uri'      => $uri,
                                'trace' => 1,
                                'exceptions' => 1,
                                'connection_timeout'=> $timeout
                                   ));
            }
    
        } catch (SoapFault $e) {
            $con = NULL;
        }
        return $con;
    }
    // End functions
    
    // First get the remote email domains and accounts
    $errorserver=array();
    $maildomain_remote=array();
    foreach ($secondaryservers as $secondaryserver) {
        if ($secondaryserver[2]!='') {
            $http_s='https://'.$secondaryserver[1].':'.$secondaryserver[2];
        } else {
            $http_s='https://'.$secondaryserver[1];
        }
        $client_remote = SoapConnect($http_s.'/remote/index.php',$http_s.'/remote/',$secondaryserver[5],$soap_timeout_remote);
        if (!is_null($client_remote)) {
            try {
                    if($session_id = $client_remote->login($secondaryserver[3],$secondaryserver[4])) {
                            echo ("Logged in successfull on ".$secondaryserver[1]." Session ID:".$session_id." \r\n");
                    }
    
                    // Get all the email domains
                    $mail_domains = $client_remote->mail_domain_get($session_id,-1);
                $marr = array();
                foreach ($mail_domains as $mail_domain) {
                    if (strtoupper($mail_domain['active']) == 'Y') {
                        $marr[] = $mail_domain['domain'];
                    }
                }
                $maildomain_remote[$secondaryserver[6]] = $marr;
                    // logout
                       if($client_remote->logout($session_id)) {
                            echo ("Remote logged out from ".$secondaryserver[1].". \r\n");
                    }
            } catch (SoapFault $e) {
                    echo $client_remote->__getLastResponse();
                    echo("SOAP Error (".$secondaryserver[1].") : ".$e->getMessage()."\r\n");
                // add this server to the errorlist
                $errorserver[]=$secondaryserver[6];
            }
        } else {
            // add this server to the errorlist
            $errorserver[]=$secondaryserver[1];
        }
    }
    
    
    // Make a local connection
    $client_local = SoapConnect($soap_location_local,$soap_uri_local,'N',$soap_timeout_local);
    // try to login
    try {
        if($session_id = $client_local->login($username_local, $password_local)) {
            echo ("Local logged in successfull Session ID:".$session_id.". \r\n");
        }
    
    
        // get the transprt list
        $mail_transports = $client_local->mail_transport_get($session_id,-1);
        // Get all the mail domains, we have to check later if this server is not allready serving this domain
        $maildomain_local = $client_local->mail_domain_get($session_id,-1);
        // Get the relay recipient list
        $mailrecipient_local = $client_local->mail_relay_recipient_get($session_id,-1);
        // Add all the mail domains from remote that's not in the mail transport list, also add the relay_recipient
        // first we loop thru all servers
        foreach ($maildomain_remote as $key => $server) {
            // now loop to all the maildomains
            foreach($server as $maildomain) {
                // search array
                if (array_search($maildomain, array_column($mail_transports, 'domain')) === false) {
                    if (array_search($maildomain, array_column($maildomain_local, 'domain')) === false) {
                    // add the record
                        $rec = array (
                            'server_id' => 1,
                            'domain' => $maildomain,
                            'transport' => 'smtp:['.$key.']',
                            'sort_order' => 5,
                            'active' => 'y'
                        );
                        $res = $client_local->mail_transport_add($session_id,$my_client_id,$rec);
                                       if ($res == -1) {
                                            echo ($maildomain." add error $res \r\n");
                                    } else {
                                            echo ($maildomain." added to mail transport \r\n");
                                    }
                        $rec = array (
                            'server_id' => 1,
                            'source' => '@'.$maildomain,
                            'active' => 'y'
                        );
                                        $res = $client_local->mail_relay_recipient_add($session_id,$my_client_id,$rec);
                                           if ($res == -1) {
                                                echo ($maildomain." add error $res \r\n");
                                        } else {
                                                echo ($maildomain." added to recipient \r\n");
                                        }
    
                    } else {
                        echo ($maildomain." is allready beeing served on this server \r\n");
                    }
                }
    
            }
        }
        // now loop thru the mail transport to delete what's  not in the maildomain remote
        foreach ($mail_transports as $mail_transport) {
            if (in_array($mail_transport['domain'],array_merge(...array_values($maildomain_remote)),true) == false) {
                $smtp_host = preg_match('~\[([^()]+)\]~',$mail_transport['transport'],$hm);
                if ((empty($hm)) || (in_array($hm[1],$errorserver) == false)) {
                    // remove the mailtransport and relay recipient
                                    $res = $client_local->mail_transport_delete($session_id,$mail_transport['transport_id']);
                                    if ($res == -1) {
                                        echo ($mail_transport['domain']." (mailtransport) delete error $res \r\n");
                                    } else {
                                            echo ($mail_transport['domain']." removed from mail transport \r\n");
                                    }
                    // find th relay recipient record
                    $recipient_rec = array_search('@'.$mail_transport['domain'],array_column($mailrecipient_local,'source'));
                    if ($recipient_rec != false) {
                        // found, now delete it
                                            $res = $client_local->mail_relay_recipient_delete($session_id,$mailrecipient_local[$recipient_rec]['relay_recipient_id']);
                                            if ($res == -1) {
                                                    echo ($mail_transport['domain']." (relay recipient) delete error $res \r\n");
                                            } else {
                                                    echo ($mail_transport['domain']." removed from relay recipient \r\n");
                                            }
    
                    }
                }
            }
        }
        // logout
        if($client_local->logout($session_id)) {
            echo ("Local logged out. \r\n");
        }
    } catch (SoapFault $e) {
        echo $client_local->__getLastResponse();
        die('SOAP Error: '.$e->getMessage());
    }
    echo ("Mail backup update Done \r\n");
    ?>
    
    
    
    
     
    TonyG, ahrasis and till like this.
  14. PDJ

    PDJ Member

    The explanation of the settings you can find in post #31
    There is on more item added to the "$secondaryservers" array: SMTP forward IP/Host, this is the IP or hostname the SMTP server will forward the mails to (for example: public IP or MX1 record)

    Please let me know if you are going to use the code, you can run this from the cron. both scripts can run as often as you like, it will check for changes, if there are non, the script won't do anything.
    If the remote server is not responding, it will not delete the records (otherwise the backup would not make sense at all)
    This is the same for the DNS script.

    With these 2 scripts you can make one ISPConfig backup of another ISPConfig server but they will be standalone and won't share customer details, except DNS and mail settings, but not login details etc.

    Enjoy
     
    TonyG, ahrasis and Taleman like this.
  15. TonyG

    TonyG Active Member

    Fascinating thread with a great finish! I am working intensely on backup and recovery right now and need to post another thread on this topic. But I absolutely will go through the code and other notes in this thread for an education.

    Fun thoughts...
    I think what @PDJ has been describing is "ISPConfig for ISPConfig" :) Where ISPC serves as a controller for common websites, PDJ has a couple ISPC instances where it seems a centralized controller could be used to keep them in sync - only for specific components. Picture a UI, in or like the ISPConfig UI, that has tabs and fields to administer a collection of other ISPConfig instances. ISPC already supports multiple role tiers: There is the ISPC administrator, who sells to resellers, and they sell to clients. This is just one level above that. That would be a cool module ... for a very limited audience! ;)
     
  16. PDJ

    PDJ Member

    Thank you for your reply.
    The idea behind this is actually de-central, if you have 2 or more ISPConfigs on different locations and you don't want to merge them (different customers, or whatever reason you have) via those 2 scripts the servers can be their backups for vital services (DNS and mail), problem with DNS going down and you don't have a backup, because of cashing, for some customers it can be down for 24 to 48 hours, even if your server was down for less then a minute, so you need a backup DNS.
    For mail, it is possible that someone is sending a mail to on of your clients when the server is down, or unreachable, because of the backup the mail will be sent to the other server, kept there until the server is reachable again.
    That's what those 2 script will accomplish, everything can be done by hand, but those 2 scripts will automate this.

    If you look like centralized maintenance or user database, I think it's better to use the build in synchronization of ISPConfig, I think that would work way better for you.
     
  17. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    May be but personally I am not really convinced with this method.
     
  18. PDJ

    PDJ Member

    You mean with the scripts I wrote?
    Why not? did I do something that's already in ISPConfig, or are you not convinced about backup of DNS and mail?
     
  19. Jesse Norell

    Jesse Norell ISPConfig Developer Staff Member ISPConfig Developer

    Backup DNS makes sense (you have implemented something similar to an "automatic slave zones" feature that has been in discussion recently), but I'm curious as to the use case for a backup mail server? There probably is one, but all I can think of is the "backup" server will spool mail for the primary if it's down, but that behavior is already built into the smtp rfc behavior anyways (it spools on the sending side, which will allow much better spam scanning once the mail server is back up).
     
  20. PDJ

    PDJ Member

    I do not agree completely, yes most mail servers will queue a mail if it can't be delivered, but there are mailserver that do not queue and let the sender know the mail can't be delivered.
    Some servers doe queue, but give up after 5 minutes or an hour. (hotmail for example gives up pretty soon)
    Most queue for a longer time, but just for the ones who do not queue (long enough) there is this backup for.
    Also, you lay the service of your hosting in the hands of another, when your server is down, you hope the IT department will back your service up by queuing the mail, if they don't, who is to blame, the mailserver that does not queue (long enough) or your webserver that shouldn't be down...
    Many hosting companies do have backup mail.
    That's my opinion.
    But therefore there are 2 separate scripts, they run independent, if you only want to use the automatic slave zone creation, that will work.

    P.S. this script works for me, but there should be some tweaks for common use, for example, every slave record that's not on the master will be deleted, for me ok, but it's easy to adjust the script a bit so that it only deletes the records made by the script (actually the script's user) if you need the adjustments, I'm happy to do so.
     
    Last edited: Oct 7, 2020

Share This Page