├── INSTALL.md ├── LICENSE ├── README.md ├── da_cloudflare_dns_sync ├── composer.json ├── config_schema.json ├── dns_write_post_cloudflare.php ├── domains │ └── default.json ├── verify_connection.php └── verify_domain_config.php └── dns_write_post.sh /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Installation Instructions (with helpful examples) 2 | 3 | You will need to have the following tools installed on your server to follow these instructions: 4 | 5 | * Composer Dependency Manager for PHP (see: https://getcomposer.org/) 6 | * git 7 | 8 |
    9 |
  1. SSH into your server (as admin)
  2. 10 |
  3. Download the repository to a folder on your server (example as follows) 11 | 15 |
  4. 16 |
  5. Install the required composer packages 17 | 21 |
  6. 22 |
  7. At this point the composer dependencies will be downloaded into the vendor subfolder by composer and everything is ready to move into place in the DirectAdmin custom scripts folder
  8. 23 |
  9. Copy all the files in the main folder you created, including all subfolders, to /usr/local/directadmin/scripts/custom 24 | 28 |
  10. 29 |
30 | 31 | ## Cloudflare setup 32 | 33 |
    34 |
  1. Create a Cloudflare account if you haven't done so already at cloudflare.com
  2. 35 |
  3. Login to your Cloudflare account
  4. 36 |
  5. If you haven't used Cloudflare before, you will need to add one of your domains to Cloudflare to see the custom nameservers that have been assigned to your account. You're welcome to get a pro plan, but the free plan also works for this step.
  6. 37 |
  7. Once you have added your domain, you'll see the two nameservers that have been assigned to you in the format name.ns.cloudflare.com - make note of these for the DirectAdmin setup below
  8. 38 |
  9. Click on your profile icon at the top right of the website
  10. 39 |
  11. Click on 'My Profile'
  12. 40 |
  13. Select the 'API Tokens' tab
  14. 41 |
  15. Under the API Keys section, click on 'View' next to 'Global API Key'
  16. 42 |
  17. Type your password to view your API Key - make a safe note of this for the script setup below
  18. 43 |
44 | 45 | ## Script setup 46 | 47 |
    48 |
  1. Edit the dns_write_post.sh file to add your email and Cloudflare API Key
  2. 49 |
  3. sudo vim /usr/local/directadmin/scripts/custom/dns_write_post.sh
  4. 50 |
  5. Edit the script to add your Cloudflare registered email address: eg. $cloudflare_email = 'email@domain.com';
  6. 51 |
  7. Edit the script to add your Cloudflare API Key: eg. $cloudflare_api_key='1234567890';
  8. 52 |
  9. Save the script and exit the editor
  10. 53 |
  11. Give dns_write_post.sh executable permissions: chmod 755 /usr/local/directadmin/scripts/custom/dns_write_post.sh
  12. 54 |
  13. Run the following command to verify the connection to Cloudflare 55 | 59 |
  14. 60 |
  15. If so, CONGRATULATIONS, the script is installed and communicating with your Cloudflare account
  16. 61 |
62 | 63 | ## DirectAdmin setup 64 |
    65 |
  1. Login to your DirectAdmin reseller or admin account
  2. 66 |
  3. Edit the DNS record for one of your websites. Change the nameserver records for the domain to your two new Cloudflare nameservers
  4. 67 |
  5. Have a look at the DNS record on your Cloudflare account - if it is a different domain than you have previously added to your Cloudflare account, you will see that it has now been added, and any differences in the DNS record have been synchronized.
  6. 68 |
  7. Obviously if you are moving your domain NS to Cloudflare for the first time, you will also need to update the nameserver records at your domain registrar.
  8. 69 |
70 | 71 | That's it! 72 | 73 | ## Troubleshooting (or just interested in watching your domain changes) 74 | 75 | Note: Any fatal errors encountered while adding/editing your domain will be displayed automatically displayed by DirectAdmin. 76 | 77 | But, if you want to see exactly what the script is doing you can enable and view all record deletions and additions by doing the following: 78 | 79 | * Edit the usr/local/directadmin/scripts/custom/dns_write_post.sh file 80 | * Set: ```$log_messages = true``` 81 | * Save the file 82 | * Watch the output in the log file. eg. ```tail -f /tmp/cloudflare_dns_messages.log``` 83 | * Edit your domain in DirectAdmin and watch the log file output 84 | * Remember to set: ```$log_messages = false``` when you have finished watching 85 | 86 |
87 | 88 | ### Notes: 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 pjjonesnz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Feb 2021 Update - daflare plugin released 2 | 3 | **PLEASE NOTE:** This script is now deprecated. For my replacement DirectAdmin Plugin, daflare, see: https://daflare.com 4 | 5 | The benefits of moving to daflare are: 6 | 7 | * GUI configuration panel integrated into DirectAdmin 8 | * Easy plugin install/update/delete using DirectAdmin plugin manager 9 | * Uses Cloudflare API tokens for better security 10 | * Share a single Cloudflare account with all the user accounts you manage 11 | 12 | [Visit daflare.com](https://daflare.com) for all the details and to download 13 | 14 | --- 15 | 16 | # DEPRECATED: DirectAdmin to Cloudflare DNS Sync 17 | 18 | ## Deprecated script instructions 19 | 20 | Script to sync DirectAdmin dns changes *from* DirectAdmin *to* your Cloudflare account 21 | 22 | 23 | * **Keep your Cloudflare dns records in sync with your server** without having to log in to Cloudflare and manually change them 24 | * **Automatically add new domains** to your Cloudflare account when they are added to your server 25 | * **Set proxy on/off** using a config file **for the entire domain or for individual records** 26 | * Set proxy on/off for all of your domains, using a **default settings file** 27 | 28 | Cloudflare has a free dns service which is super fast. It is a great way to move nameserver hosting off your web server and on to a managed platform. And if you want to go even faster, enable their proxy! 29 | 30 | This handy script for DirectAdmin syncs the DNS records for your domains FROM DirectAdmin TO your Cloudflare account. Use this script when using Cloudflare as the nameserver host for your domain. 31 | 32 | I use this script in a production environment hosting multiple domains. 33 | 34 | ## Installation 35 | 36 | [Full Installation Instructions Here](./INSTALL.md) 37 | 38 | ## Usage 39 | 40 | 1. create/modify your domain in DirectAdmin 41 | 2. set your DirectAdmin domain's NS records to customname.ns.cloudflare.com AND customname2.ns.cloudflare.com (where customname/customname2 are the ns record names assigned to you by Cloudflare. 42 | 3. Check your Cloudflare account and see the domain settings automatically synchronized with your server's settings. 43 | 44 | **NOTE:** If you have already added your domain to Cloudflare and have changed your DNS settings manually there, and they are different from your server, your server will overwrite any settings on Cloudflare and delete any settings that don't exist on your server. The synchronization is one way, *from* your server *to* Cloudflare. 45 | 46 | ## Customize your proxy settings 47 | 48 | Proxy configuration is found in this folder: /usr/local/directadmin/scripts/custom/da_cloudflare_dns_sync/domains 49 | 50 | ### Proxy defaults 51 | 52 | The default settings for your server can be made in: 'default.json' 53 | 54 | | Setting | Description | Default | 55 | |---|---|---| 56 | | ```proxy_default``` | the default proxy setting for all valid records (A, CNAME and AAAA) | false | 57 | | ```proxy_record``` | an object that contains individual settings by record type and name | | 58 | 59 | Example to enable Cloudflare proxy on ALL DOMAINS, but disable it for ftp and mail A records: 60 | 61 | ```js 62 | { 63 | "proxy_default": true, 64 | "proxy_record": { 65 | "A": { 66 | "mail": false, 67 | "ftp": false 68 | }, 69 | "CNAME": { 70 | }, 71 | "AAAA": { 72 | } 73 | } 74 | } 75 | ``` 76 | 77 | ## Testing your config files 78 | 79 | All config files in the domains folder can be tested by running the following command ```/usr/local/directadmin/scripts/custom/dns_write_post.sh test_config``` 80 | 81 | Any errors while decoding the json file will be displayed. Any errors in the config options will also be displayed along with the json node tree that contains the error. If you have trouble finding what is causing the error feel free to open an issue. 82 | 83 | You can also test that your JSON file format is correct at [JSONLint](https://jsonlint.com/) - although this doesn't check that the config properties are correct. 84 | 85 | Note: Be careful that you don't have any trailing commas in your json file. Remember that json isn't the same as javascript object literal syntax. 86 | 87 | ## Settings for individual domains 88 | 89 | The proxy settings can also be enabled/disabled for particular domains 90 | 91 | * Copy the default.json file to **my_full_domain_name**.json (eg. ```cp default.json mydomainname.com.json```) 92 | * Change the settings in the newly create file for your domain as required 93 | 94 | Example to enable proxy on your domain record and www but disable it on everything else: 95 | 96 | ```js 97 | // Example domain name: mydomainname.com 98 | // Filename: mydomainname.com.json 99 | 100 | { 101 | "proxy_default": false, 102 | "proxy_record": { 103 | "A": { // Add A records here 104 | "mydomainname.com.": true, 105 | "www": true 106 | }, 107 | "CNAME": { // Add CNAME records here 108 | }, 109 | "AAAA": { // Add AAAA records here 110 | } 111 | } 112 | } 113 | ``` 114 | **Please note:** comments in the above example must be removed as comments are not valid json. 115 | 116 | ## Setting proxy on/off for entire record type 117 | 118 | You can set the record type in the following two ways: 119 | 120 | 1. to an object (as per the example above), with a list of records you want to enable (or disable) the proxy for. 121 | 2. to a boolean, to set all records of that type to be proxied (or not). eg. 122 | 123 | ```js 124 | { 125 | "proxy_default": false, 126 | "proxy_record": { 127 | "A": true, 128 | "CNAME": { 129 | "my.record": true, 130 | }, 131 | "AAAA": { 132 | } 133 | } 134 | } 135 | ``` 136 | 137 | 138 | 139 | To cause your domain to update after changing any settings, edit one of the domain records and save your settings (even making it equal to the same settings as it was before will force an update of any changed records). 140 | 141 | I hope this script is helpful to you. PLEASE let me know if you have any troubles by creating an issue in Github. 142 | -------------------------------------------------------------------------------- /da_cloudflare_dns_sync/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "cloudflare/sdk": "dev-pjjonesnz-fix-mx-priority-0", 4 | "opis/json-schema": "^1.0" 5 | }, 6 | "repositories": [ 7 | { 8 | "type": "vcs", 9 | "url": "https://github.com/pjjonesnz/cloudflare-php" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /da_cloudflare_dns_sync/config_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "type": "object", 4 | "properties": { 5 | "proxy_default": { 6 | "type": "boolean" 7 | }, 8 | "proxy_record": { 9 | "type": "object", 10 | "additionalProperties": false, 11 | "properties": { 12 | "A": { 13 | "type": ["object","boolean"], 14 | "propertyNames": { 15 | "type": "string" 16 | }, 17 | "patternProperties": { 18 | ".*": { 19 | "type": "boolean" 20 | } 21 | } 22 | }, 23 | "CNAME": { 24 | "type": ["object","boolean"], 25 | "propertyNames": { 26 | "type": "string" 27 | }, 28 | "patternProperties": { 29 | ".*": { 30 | "type": "boolean" 31 | } 32 | } 33 | }, 34 | "AAAA": { 35 | "type": ["object","boolean"], 36 | "propertyNames": { 37 | "type": "string" 38 | }, 39 | "patternProperties": { 40 | ".*": { 41 | "type": "boolean" 42 | } 43 | } 44 | } 45 | } 46 | } 47 | }, 48 | "required": ["proxy_default"], 49 | "additionalProperties": false 50 | } 51 | -------------------------------------------------------------------------------- /da_cloudflare_dns_sync/dns_write_post_cloudflare.php: -------------------------------------------------------------------------------- 1 | getZoneID($domain); 57 | } catch (\Exception $e) { 58 | if ($e->getMessage() == 'Could not find zones with specified name.') { 59 | // Create zone 60 | try { 61 | $result = $zones->addZone($domain); 62 | if ($result->name == $domain) { 63 | $zoneID = $result->id; 64 | } else { 65 | throw new Exception("Error Adding Domain", 1); 66 | } 67 | } catch (\Exception $e) { 68 | logMessage('Exception Caught: ' . $e->getMessage()); 69 | exit; 70 | } 71 | } else { 72 | exit; 73 | } 74 | } 75 | 76 | logMessage('Zone ID for ' . $domain . ' - ' . $zoneID); 77 | 78 | /** 79 | * Load settings json for domain, or defaults if custom domain settings not available 80 | */ 81 | $domain_settings = array('proxy_default' => false); 82 | $settings_filename = __DIR__ . '/domains/' . $domain . '.json'; 83 | 84 | if (!file_exists($settings_filename)) { 85 | $settings_filename = __DIR__ . '/domains/default.json'; 86 | if (!file_exists($settings_filename)) { 87 | logMessage('Error: default domain config file default.json missing from plugin.', true); 88 | $settings_filename = ''; 89 | } 90 | } 91 | if ($settings_filename !== '') { 92 | $json_file = file_get_contents($settings_filename); 93 | if ($json_file !== false) { 94 | $load_settings = json_decode($json_file, true); 95 | if ($load_settings !== NULL) { 96 | $domain_settings = $load_settings; 97 | } else { 98 | logMessage('Error: Json error in default.json plugin config file.', true); 99 | } 100 | } 101 | } 102 | 103 | /** 104 | * Load existing DNS records for the domain 105 | */ 106 | $dns = new \Cloudflare\API\Endpoints\DNS($adapter); 107 | $page = 0; 108 | $per_page = 500; 109 | $existingRecords = array(); 110 | 111 | do { 112 | $page++; 113 | $listRecords = $dns->listRecords($zoneID, '', '', '', $page, $per_page); 114 | $existingRecords = array_merge($existingRecords, $listRecords->result); 115 | } while ($listRecords->result_info->total_pages > $page); 116 | 117 | /** 118 | * Array of dns records to add to Cloudflare 119 | */ 120 | $recordsToAdd = array(); 121 | 122 | /** 123 | * Array of dns records that already exist on Cloudflare 124 | */ 125 | $recordsThatExist = array(); 126 | 127 | // NOTE don't parse NS records 128 | 129 | // parse A records 130 | $output = parseInput($a); 131 | if (count($output) > 0) { 132 | foreach ($output as $value) { 133 | $record = (object) array( 134 | 'type' => 'A', 135 | 'name' => qualifyRecordName($value->key), 136 | 'content' => $value->value, 137 | 'ttl' => $aTTL, 138 | 'proxied' => is_proxied('A', $value->key), 139 | ); 140 | if (doesRecordExist($existingRecords, $record) === false) { 141 | $recordsToAdd[] = $record; 142 | } else { 143 | $recordsThatExist[] = $record; 144 | } 145 | } 146 | } 147 | 148 | // parse TXT records 149 | $output = parseInput($txt); 150 | if (count($output) > 0) { 151 | foreach ($output as $value) { 152 | $record = (object) array( 153 | 'type' => 'TXT', 154 | 'name' => qualifyRecordName($value->key), 155 | 'content' => trim(preg_replace(array('/"\s+"/', '/"/'), array('', ''), trim($value->value, '()'))), 156 | 'ttl' => $txtTTL, 157 | 'proxied' => false 158 | ); 159 | if (doesRecordExist($existingRecords, $record) === false) { 160 | $recordsToAdd[] = $record; 161 | } else { 162 | $recordsThatExist[] = $record; 163 | } 164 | } 165 | } 166 | 167 | // parse MX records 168 | $output = parseInput($mx_full); 169 | if (count($output) > 0) { 170 | foreach ($output as $value) { 171 | preg_match('/(\d+) (.*)/', $value->value, $parsedValue); 172 | $record = (object) array( 173 | 'type' => 'MX', 174 | 'name' => qualifyRecordName($value->key), 175 | 'priority' => $parsedValue[1], 176 | 'content' => qualifyRecordName($parsedValue[2]), 177 | 'ttl' => $mxTTL, 178 | 'proxied' => false, 179 | ); 180 | if (doesRecordExist($existingRecords, $record) === false) { 181 | $recordsToAdd[] = $record; 182 | } else { 183 | $recordsThatExist[] = $record; 184 | } 185 | } 186 | } 187 | 188 | // parse CNAME records 189 | $output = parseInput($cname); 190 | if (count($output) > 0) { 191 | foreach ($output as $value) { 192 | $record = (object) array( 193 | 'type' => 'CNAME', 194 | 'name' => qualifyRecordName($value->key), 195 | 'content' => qualifyRecordName($value->value), 196 | 'ttl' => $cnameTTL, 197 | 'proxied' => is_proxied('CNAME', $value->key), 198 | ); 199 | if (doesRecordExist($existingRecords, $record) === false) { 200 | $recordsToAdd[] = $record; 201 | } else { 202 | $recordsThatExist[] = $record; 203 | } 204 | } 205 | } 206 | 207 | // parse PTR records 208 | $output = parseInput($ptr); 209 | if (count($output) > 0) { 210 | foreach ($output as $value) { 211 | $record = (object) array( 212 | 'type' => 'PTR', 213 | 'name' => $value->key, 214 | 'content' => qualifyRecordName($value->value), 215 | 'ttl' => $ptrTTL, 216 | 'proxied' => false, 217 | ); 218 | if (doesRecordExist($existingRecords, $record) === false) { 219 | $recordsToAdd[] = $record; 220 | } else { 221 | $recordsThatExist[] = $record; 222 | } 223 | } 224 | } 225 | 226 | // parse AAAA records 227 | $output = parseInput($aaaa); 228 | if (count($output) > 0) { 229 | foreach ($output as $value) { 230 | $record = (object) array( 231 | 'type' => 'AAAA', 232 | 'name' => qualifyRecordName($value->key), 233 | 'content' => $value->value, 234 | 'ttl' => $aaaaTTL, 235 | 'proxied' => is_proxied('AAAA', $value->key), 236 | ); 237 | if (doesRecordExist($existingRecords, $record) === false) { 238 | $recordsToAdd[] = $record; 239 | } else { 240 | $recordsThatExist[] = $record; 241 | } 242 | } 243 | } 244 | 245 | // parse SRV records 246 | $output = parseInput($srv); 247 | if (count($output) > 0) { 248 | 249 | foreach ($output as $value) { 250 | preg_match('/(\d+) (\d+) (\d+) (.*)/', $value->value, $parsedValue); 251 | 252 | $fullSRVname = qualifyRecordName($value->key); 253 | preg_match('/^(.*)\._(tcp|udp|tls)\.(.*)$/', $fullSRVname, $srv_match); 254 | 255 | $record = (object) array( 256 | 'type' => 'SRV', 257 | 'name' => $fullSRVname, 258 | 'priority' => $parsedValue[1], 259 | 'content' => qualifyRecordName($parsedValue[4]), 260 | 'data' => array( 261 | 'name' => $srv_match[3], 262 | 'weight' => (int) $parsedValue[2], 263 | 'port' => (int) $parsedValue[3], 264 | 'target' => qualifyRecordName($parsedValue[4]), 265 | 'proto' => '_' . $srv_match[2], 266 | 'service' => $srv_match[1], 267 | 'priority' => (int) $parsedValue[1], 268 | ), 269 | 'ttl' => $srvTTL, 270 | 'proxied' => false, 271 | ); 272 | if (doesRecordExist($existingRecords, $record) === false) { 273 | $recordsToAdd[] = $record; 274 | } else { 275 | $recordsThatExist[] = $record; 276 | } 277 | } 278 | } 279 | 280 | /** 281 | * Delete records from Cloudflare that don't exist in DirectAdmin 282 | * 283 | * While ignoring nameserver records, go through all existingRecords and see if there is a match in recordsToAdd or recordsThatExist - if not, delete the record. 284 | */ 285 | $keysToDelete = array(); 286 | foreach ($existingRecords as $record) { 287 | if ($record->type != 'NS') { 288 | if (!isRecordCurrent($recordsToAdd, $recordsThatExist, $record)) { 289 | array_push($keysToDelete, $record->id); 290 | logMessage('Queue Record to Delete: ' . $record->id . ' - ' . $record->name . "\t" . $record->type . "\t" . $record->content); 291 | } 292 | } 293 | } 294 | if (count($keysToDelete) > 0) { 295 | foreach ($keysToDelete as $key) { 296 | $success = $dns->deleteRecord($zoneID, $key); 297 | logMessage('Delete Record ID ' . $key . "\t" . ($success == true ? 'SUCCESSFUL' : 'FAILED')); 298 | } 299 | } 300 | 301 | /** 302 | * Add new records to Cloudflare 303 | */ 304 | foreach ($recordsToAdd as $record) { 305 | $priority = isset($record->priority) ? $record->priority : ''; 306 | $data = isset($record->data) && count($record->data) > 0 ? $record->data : []; 307 | $proxied = $record->proxied; 308 | $ttl = $record->ttl > 0 ? $record->ttl : 0; // use default TTL 309 | try { 310 | $success = $dns->addRecord($zoneID, $record->type, $record->name, $record->content, $ttl, $proxied, $priority, $data); 311 | logMessage('Add Record: ' . $record->name . "\t" . $record->type . "\t" . $record->content . "\t" . ($success == true ? 'SUCCESSFUL' : 'FAILED') . "\t" . "Proxy: " . ($proxied ? 'on' : 'off'), !$success); 312 | } catch (\GuzzleHttp\Exception\ClientException $e) { 313 | logMessage('Add Record: ' . $record->name . "\t" . $record->type . "\t" . $record->content . "\t" . 'FAILED', true); 314 | logMessage($e->getResponse()->getBody()->getContents(), true); 315 | } 316 | } 317 | 318 | logMessage('Result: ' . get_records_modified_text($keysToDelete) . ' deleted, ' . get_records_modified_text($recordsToAdd) . ' added.'); 319 | 320 | // Output any errors to be displayed in the DirectAdmin console 321 | if ($hasErrors) { 322 | echo join("\n\n", $errorList); 323 | exit(1); 324 | } else { 325 | exit(0); 326 | } 327 | 328 | function is_proxied($record_type, $record_name) 329 | { 330 | global $domain_settings; 331 | if($record_name == 'localhost') { 332 | return false; 333 | } 334 | if (isset($domain_settings['proxy_record']) && isset($domain_settings['proxy_record'][$record_type])) { 335 | if(is_array($domain_settings['proxy_record'][$record_type])) { 336 | if(isset($domain_settings['proxy_record'][$record_type][$record_name])) { 337 | return $domain_settings['proxy_record'][$record_type][$record_name] === true; 338 | } 339 | } 340 | elseif(is_bool($domain_settings['proxy_record'][$record_type]) ) { 341 | return $domain_settings['proxy_record'][$record_type] === true; 342 | } 343 | } 344 | return isset($domain_settings['proxy_default']) && $domain_settings['proxy_default'] === true; 345 | } 346 | 347 | function get_records_modified_text($mod_array) 348 | { 349 | if (count($mod_array) == 1) { 350 | return '1 record'; 351 | } 352 | return count($mod_array) . ' records'; 353 | } 354 | 355 | /** 356 | * Log a message to the php error log 357 | */ 358 | function logMessage($message, $error = false) 359 | { 360 | global $log_messages, $log_to_file, $log_filename, $errorList, $hasErrors; 361 | if ($error) { 362 | $hasErrors = true; 363 | $errorList[] = $message; 364 | } 365 | if (!$log_messages) { 366 | return; 367 | } 368 | $timestamp = date('Y-m-d H:i:s'); 369 | if ($log_to_file) { 370 | error_log($timestamp . " - " . $message . "\n", 3, $log_filename); 371 | } else { 372 | error_log($timestamp . " - " . $message . "\n"); 373 | } 374 | } 375 | 376 | /** 377 | * Search through array of records for a particular record 378 | */ 379 | function doesRecordExist($records, $record) 380 | { 381 | global $use_da_ttl; 382 | if (count($records) == 0) { 383 | return false; 384 | } 385 | foreach ($records as $compare) { 386 | if ($record->type == 'SRV') { 387 | if (!isset($compare->data)) { 388 | continue; 389 | } 390 | $compare_data = (object) $compare->data; 391 | $record_data = (object) $record->data; 392 | if ( 393 | compare_records($compare->type, $record->type) && 394 | compare_records($compare->name, $record->name) && 395 | compare_records($compare->proxied, $record->proxied) && 396 | (!$use_da_ttl || compare_records($compare->ttl, $record->ttl)) && 397 | compare_records($compare_data->weight, $record_data->weight) && 398 | compare_records($compare_data->target, $record_data->target) && 399 | compare_records($compare_data->proto, $record_data->proto) && 400 | compare_records($compare_data->service, $record_data->service) && 401 | compare_records($compare_data->priority, $record_data->priority) && 402 | compare_records($compare_data->port, $record_data->port) 403 | ) { 404 | return true; 405 | } 406 | } else { 407 | if ( 408 | compare_records($compare->type, $record->type) && 409 | compare_records($compare->name, $record->name) && 410 | compare_records($compare->content, $record->content) && 411 | compare_records($compare->proxied, $record->proxied) && 412 | (!$use_da_ttl || compare_records($compare->ttl, $record->ttl)) 413 | ) { 414 | if ($record->type == 'MX' && !compare_records($compare->priority, $record->priority)) { 415 | continue; 416 | } 417 | return true; 418 | } 419 | } 420 | } 421 | return false; 422 | } 423 | 424 | /** 425 | * Compare two records to see if they match 426 | * @param string $r1 Existing Record on Cloudflare 427 | * @param string $r2 Record on server 428 | */ 429 | function compare_records($r1, $r2) 430 | { 431 | return $r1 == $r2; 432 | } 433 | 434 | /** 435 | * Search through recordsToAdd and recordsThatExist to see if the existing record should be kept or deleted 436 | */ 437 | function isRecordCurrent($recordsToAdd, $recordsThatExist, $record) 438 | { 439 | if (count($recordsToAdd) > 0) { 440 | if (doesRecordExist($recordsToAdd, $record)) { 441 | return true; 442 | } 443 | } 444 | if (count($recordsThatExist) > 0) { 445 | if (doesRecordExist($recordsThatExist, $record)) { 446 | return true; 447 | } 448 | } 449 | return false; 450 | } 451 | 452 | /** 453 | * Parse Environment variables from DirectAdmin and split into key and value pairs 454 | */ 455 | function parseInput($input) 456 | { 457 | $pairs = explode('&', $input); 458 | $results = array(); 459 | foreach ($pairs as $pair) { 460 | if ($pair == "") continue; 461 | list($key, $value) = explode('=', $pair, 2); 462 | if ($key == "" || $value == "") continue; 463 | $object = new StdClass(); 464 | $object->key = $key; 465 | $object->value = $value; 466 | $results[] = $object; 467 | } 468 | return $results; 469 | } 470 | 471 | /** 472 | * Fully qualify the record name 473 | */ 474 | function qualifyRecordName($name) 475 | { 476 | global $domain; 477 | return trim($name . (substr($name, -1) !== '.' ? '.' . $domain : ''), '.'); 478 | } 479 | -------------------------------------------------------------------------------- /da_cloudflare_dns_sync/domains/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "proxy_default": false, 3 | "proxy_record": { 4 | "A": { 5 | }, 6 | "CNAME": { 7 | }, 8 | "AAAA": { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /da_cloudflare_dns_sync/verify_connection.php: -------------------------------------------------------------------------------- 1 | getUserID() . PHP_EOL; 12 | 13 | ?> -------------------------------------------------------------------------------- /da_cloudflare_dns_sync/verify_domain_config.php: -------------------------------------------------------------------------------- 1 | schemaValidation($data, $schema); 73 | 74 | if ($result->isValid()) { 75 | echo "\tConfig file is valid", PHP_EOL; 76 | echo "\n\tResult: SUCCESS", PHP_EOL; 77 | } else { 78 | /** @var ValidationError $error */ 79 | $error = $result->getFirstError(); 80 | echo "\tConfig file is invalid", PHP_EOL; 81 | $error_node = $error->dataPointer(); 82 | echo "\tNode containing the error: " . json_encode($error_node), PHP_EOL; 83 | $error_keyword = $error->keyword(); 84 | if ($error_keyword == 'patternProperties') { 85 | $error_keyword = "JSON values must be boolean (true or false) without speechmarks"; 86 | } 87 | 88 | if ($error_keyword == 'additionalProperties' && count(array_diff(array("proxy_record"), $error_node)) == 0) { 89 | $error_keyword = "The only domain records that Cloudflare can proxy are A, CNAME and AAAA.\n\tInvalid record type found in file.\n"; 90 | } 91 | echo "\tError: ", $error_keyword, PHP_EOL; 92 | 93 | if (count($error->keywordArgs()) > 0) { 94 | echo "\n" . json_encode($error->keywordArgs()), PHP_EOL; 95 | } 96 | echo "\n\tResult: FAIL", PHP_EOL; 97 | } 98 | echo "\n---------------------------------------------------------------------------------\n\n"; 99 | } 100 | -------------------------------------------------------------------------------- /dns_write_post.sh: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/php 2 | --------------------------------------------------------------------------------