Project

General

Profile

Download (129 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * dyndns.class
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8
 * Copyright (c) 2014-2023 Rubicon Communications, LLC (Netgate)
9
 * All rights reserved.
10
 *
11
 * Licensed under the Apache License, Version 2.0 (the "License");
12
 * you may not use this file except in compliance with the License.
13
 * You may obtain a copy of the License at
14
 *
15
 * http://www.apache.org/licenses/LICENSE-2.0
16
 *
17
 * Unless required by applicable law or agreed to in writing, software
18
 * distributed under the License is distributed on an "AS IS" BASIS,
19
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
 * See the License for the specific language governing permissions and
21
 * limitations under the License.
22
 */
23

    
24
	/*
25
	 * PHP.updateDNS (pfSense version)
26
	 *
27
	 * +====================================================+
28
	 *  Services Supported:
29
	 *    - All-Inkl (all-inkl.com)
30
	 *    - Amazon Route 53 (aws.amazon.com)
31
	 *    - Azure DNS (azure.microsoft.com)
32
	 *    - City Network (citynetwork.se)
33
	 *    - Cloudflare (www.cloudflare.com)
34
	 *    - Cloudflare IPv6 (www.cloudflare.com)
35
	 *    - ClouDNS (www.cloudns.net)
36
	 *    - Custom DDNS (any URL)
37
	 *    - Custom DDNS IPv6 (any URL)
38
	 *    - deSEC (desec.io)
39
	 *    - DHS (www.dhs.org)
40
	 *    - DNS Made Easy (www.dnsmadeeasy.com)
41
	 *    - DNS-O-Matic (dnsomatic.com)
42
	 *    - DNSexit (dnsexit.com)
43
	 *    - DNSimple (dnsimple.com)
44
	 *    - DreamHost DNS (www.dreamhost.com)
45
	 *    - DuiaDNS (www.duiadns.net)
46
	 *    - DuiaDNS IPv6 (www.duiadns.net)
47
	 *    - DY.fi (dy.fi)
48
	 *    - DynDns (dyndns.org) [dynamic, static, custom]
49
	 *    - DynS (dyns.org)
50
	 *    - Dynv6 (www.dynv6.com)
51
	 *    - EasyDNS (easydns.com)
52
	 *    - EasyDNS IPv6 (easydns.com)
53
	 *    - Eurodns (eurodns.com)
54
	 *    - FreeDNS API v1 (freedns.afraid.org)
55
	 *    - FreeDNS API v2 (freedns.afraid.org)
56
	 *    - FreeDNS IPv6 API v1 (freedns.afraid.org)
57
	 *    - FreeDNS IPv6 API v2 (freedns.afraid.org)
58
	 *    - Gandi LiveDNS (www.gandi.net)
59
	 *    - GleSYS (glesys.com)
60
	 *    - GoDaddy (www.godaddy.com)
61
	 *    - Google Domains (domains.google.com)
62
	 *    - GratisDNS (gratisdns.dk)
63
	 *    - HE.net (dns.he.net)
64
	 *    - HE.net IPv6 (dns.he.net)
65
	 *    - HE.net Tunnelbroker IP update (ipv4.tunnelbroker.net)
66
	 *    - HN (hn.org) -- incomplete checking!
67
	 *    - Hover (www.hover.com)
68
	 *    - Loopia (loopia.se)
69
	 *    - Name.com (name.com)
70
	 *    - Name.com IPv6 (name.com)
71
	 *    - Namecheap (namecheap.com)
72
	 *    - No-IP (no-ip.com)
73
	 *    - OpenDNS (opendns.com)
74
	 *    - SelfHost (selfhost.de)
75
	 *    - SPDYN (spdyn.de)
76
	 *    - SPDYN IPv6 (spdyn.de)
77
	 *    - StaticCling (staticcling.org)
78
	 *    - Strato (www.strato.de)
79
	 *    - ZoneEdit (zoneedit.com)
80
	 * +----------------------------------------------------+
81
	 *  Requirements:
82
	 *    - PHP version 4.0.2 or higher with the CURL Library and the PCRE Library
83
	 * +----------------------------------------------------+
84
	 *  Public Functions
85
	 *    - updatedns()
86
	 *
87
	 *  Private Functions
88
	 *    - _update()
89
	 *    - _checkStatus()
90
	 *    - _error()
91
	 *    - _detectChange()
92
	 *    - _debug()
93
	 *    - _checkIP()
94
	 * +----------------------------------------------------+
95
	 *  All-Inkl        - Last Tested: 12 November 2016
96
	 *  Amazon Route 53 - Last Tested: 04 February 2017
97
	 *  Azure DNS       - Last Tested: 08 March 2018
98
	 *  City Network    - Last Tested: 13 November 2013
99
	 *  Cloudflare      - Last Tested: 05 September 2016
100
	 *  Cloudflare IPv6 - Last Tested: 17 July 2016
101
	 *  ClouDNS         - Last Tested: 22 August 2017
102
	 *  deSEC           - Last Tested: NEVER
103
	 *  deSEC IPv6      - Last Tested: NEVER
104
	 *  DHS             - Last Tested: 12 July 2005
105
	 *  DigitalOcean    - Not Yet Tested
106
	 *  DNS Made Easy   - Last Tested: 27 April 2015
107
	 *  DNS-O-Matic     - Last Tested: 9 September 2010
108
	 *  DNSexit         - Last Tested: 27 June 2022
109
	 *  DNSimple        - Last Tested: 09 February 2015
110
	 *  DreamHost       - Last Tested: 30 April 2017
111
	 *  DreamHost IPv6  - Not Yet Tested
112
	 *  DuiaDNS         - Last Tested: 25 November 2016
113
	 *  DuiaDNS IPv6    - Last Tested: 25 November 2016
114
	 *  DY.fi           - Last Tested: 22 April 2021
115
	 *  DynDNS Custom   - Last Tested: NEVER
116
	 *  DynDNS Dynamic  - Last Tested: 12 July 2005
117
	 *  DynDNS Static   - Last Tested: NEVER
118
	 *  Dyns            - Last Tested: NEVER
119
	 *  EasyDNS         - Last Tested: 20 July 2008
120
	 *  Eurodns         - Last Tested: 27 June 2013
121
	 *  FreeDNS         - Last Tested: 01 May 2016
122
	 *  FreeDNS IPv6    - Last Tested: 01 May 2016
123
	 *  FreeDNS IPv6 v2 - Last Tested: 01 June 2020
124
	 *  FreeDNS v2      - Last Tested: 01 June 2020
125
	 *  Gandi LiveDNS   - Not Yet Tested
126
	 *  GleSYS          - Last Tested: 3 February 2015
127
	 *  GoDaddy         - Last Tested: 22 November 2017
128
	 *  GoDaddy IPv6    - Last Tested: 22 November 2017
129
	 *  Google Domains  - Last Tested: 27 April 2015
130
	 *  GratisDNS       - Last Tested: 15 August 2012
131
	 *  HE.net          - Last Tested: 7 July 2013
132
	 *  HE.net IPv6     - Last Tested: 7 July 2013
133
	 *  HE.net Tunnel   - Last Tested: 28 June 2011
134
	 *  HN.org          - Last Tested: 12 July 2005
135
	 *  Hover           - Last Tested: 15 February 2017
136
	 *  Loopia          - Last Tested: 21 August 2019
137
	 *  Name.com        - Last Tested: 5 Dec 2021
138
	 *  Name.com IPv6   - Last Tested: 5 Dec 2021
139
	 *  Namecheap       - Last Tested: 31 August 2010
140
	 *  No-IP           - Last Tested: 20 July 2008
141
	 *  ODS             - Last Tested: 02 August 2005
142
	 *  OpenDNS         - Last Tested: 4 August 2008
143
	 *  OVH DynHOST     - Last Tested: NEVER
144
	 *  SelfHost        - Last Tested: 26 December 2011
145
	 *  SPDYN           - Last Tested: 02 July 2016
146
	 *  SPDYN IPv6      - Last Tested: 02 July 2016
147
	 *  StaticCling     - Last Tested: 27 April 2006
148
	 *  Strato          - Last Tested: 29 May 2021
149
	 *  ZoneEdit        - Last Tested: NEVER
150
	 * +====================================================+
151
	 *
152
	 * @author 	E.Kristensen
153
	 * @link    	http://www.idylldesigns.com/projects/phpdns/
154
	 * @version 	0.8
155
	 * @updated	13 October 05 at 21:02:42 GMT
156
	 *
157
	 * DNSexit/OpenDNS support and multiwan extension for pfSense by Ermal Luçi and Koen Zomers
158
	 * Custom DNS support by Matt Corallo
159
	 *
160
	 */
161

    
162
	class updatedns {
163
		var $_cacheFile;
164
		var $_cacheFile_v6;
165
		var $_debugFile;
166
		var $_UserAgent = 'phpDynDNS/0.7';
167
		var $_errorVerbosity = 0;
168
		var $_dnsService;
169
		var $_dnsUser;
170
		var $_dnsPass;
171
		var $_dnsHost;
172
		var $_dnsDomain;
173
		var $_FQDN;
174
		var $_dnsIP;
175
		var $_dnsWildcard;
176
		var $_dnsProxied;
177
		var $_dnsMX;
178
		var $_dnsBackMX;
179
		var $_dnsServer;
180
		var $_dnsPort;
181
		var $_dnsUpdateURL;
182
		var $_dnsZoneID;
183
		var $_dnsTTL;
184
		var $status;
185
		var $_debugID;
186
		var $_if;
187
		var $_dnsResultMatch;
188
		var $_dnsRequestIf;
189
		var $_dnsRequestIfIP;
190
		var $_dnsVerboseLog;
191
		var $_curlIpresolveV4;
192
		var $_curlSslVerifypeer;
193
		var $_dnsMaxCacheAgeDays;
194
		var $_dnsDummyUpdateDone;
195
		var $_forceUpdateNeeded;
196
		var $_useIPv6;
197
		var $_existingRecords;
198
		var $_curlProxy;
199

    
200
		/*
201
		 * Public Constructor Function (added 12 July 05) [beta]
202
		 *   - Gets the dice rolling for the update.
203
		 *   - $dnsResultMatch should only be used with $dnsService = 'custom'
204
		 *   -  $dnsResultMatch is parsed for '%IP%', which is the IP the provider was updated to,
205
		 *   -  it is otherwise expected to be exactly identical to what is returned by the Provider.
206
		 *   - $dnsUser, and $dnsPass indicate HTTP Auth for custom DNS, if they are needed in the URL (GET Variables), include them in $dnsUpdateURL.
207
		 *   - $For custom requests, $dnsUpdateURL is parsed for '%IP%', which is replaced with the new IP.
208
		 */
209
		function __construct($dnsService = '', $dnsHost = '', $dnsDomain = '', $dnsUser = '', $dnsPass = '',
210
					$dnsWildcard = 'OFF', $dnsProxied = false, $dnsMX = '', $dnsIf = '', $dnsBackMX = '',
211
					$dnsServer = '', $dnsPort = '', $dnsUpdateURL = '', $forceUpdate = false,
212
					$dnsZoneID ='', $dnsTTL='', $dnsResultMatch = '', $dnsRequestIf = '', $dnsMaxCacheAge = '',
213
					$dnsID = '', $dnsVerboseLog = false, $curlIpresolveV4 = false, $curlSslVerifypeer = true,
214
					$curlProxy = false) {
215

    
216
			global $config, $g, $dyndns_split_domain_types;
217
			if (in_array($dnsService, $dyndns_split_domain_types)) {
218
				$this->_FQDN = $dnsHost . "." . $dnsDomain;
219
			} else {
220
				$this->_FQDN = $dnsHost;
221
			}
222

    
223
			$this->_cacheFile = "{$g['conf_path']}/dyndns_{$dnsIf}{$dnsService}" . escapeshellarg($this->_FQDN) . "{$dnsID}.cache";
224
			$this->_cacheFile_v6 = "{$g['conf_path']}/dyndns_{$dnsIf}{$dnsService}" . escapeshellarg($this->_FQDN) . "{$dnsID}_v6.cache";
225
			$this->_debugFile = "{$g['varetc_path']}/dyndns_{$dnsIf}{$dnsService}" . escapeshellarg($this->_FQDN) . "{$dnsID}.debug";
226

    
227
			$this->_curlIpresolveV4 = $curlIpresolveV4;
228
			$this->_curlSslVerifypeer = $curlSslVerifypeer;
229
			$this->_curlProxy = $curlProxy;
230
			$this->_dnsVerboseLog = $dnsVerboseLog;
231
			if ($this->_dnsVerboseLog) {
232
				log_error(gettext("Dynamic DNS: updatedns() starting"));
233
			}
234

    
235
			$dyndnslck = lock("DDNS" . $dnsID, LOCK_EX);
236

    
237
			if (!$dnsService) $this->_error(2);
238
			switch ($dnsService) {
239
			case 'custom':
240
			case 'custom-v6':
241
				if (!$dnsUpdateURL) $this->_error(7);
242
				break;
243
			// providers in an alphabetical order (based on the first provider in a group of cases)
244
			case 'azure':
245
			case 'azurev6':
246
				if (!$dnsUser) $this->_error(3);
247
				if (!$dnsPass) $this->_error(4);
248
				if (!$dnsHost) $this->_error(5);
249
				if (!$dnsZoneID) $this->_error(8);
250
				if (!$dnsTTL) $this->_error(9);
251
				break;
252
			case 'cloudflare':
253
			case 'cloudflare-v6':
254
				if (!$dnsPass) $this->_error(4);
255
				if (!$dnsHost) $this->_error(5);
256
				if (!$dnsDomain) $this->_error(5);
257
				break;
258
			case 'cloudns':
259
			case 'godaddy':
260
			case 'godaddy-v6':
261
				if (!$dnsUser) $this->_error(3);
262
				if (!$dnsPass) $this->_error(4);
263
				if (!$dnsHost) $this->_error(5);
264
				if (!$dnsDomain) $this->_error(5);
265
				if (!$dnsTTL) $this->_error(9);
266
				break;
267
			case 'desec':
268
			case 'desec-v6':
269
				if (!$dnsPass) $this->_error(4);
270
				if (!$dnsHost) $this->_error(5);
271
				break;
272
			case 'digitalocean':
273
			case 'digitalocean-v6':
274
			case 'gandi-livedns':
275
			case 'gandi-livedns-v6':
276
			case 'linode':
277
			case 'linode-v6':
278
			case 'onecom':
279
			case 'onecom-v6':
280
			case 'yandex':
281
			case 'yandex-v6':
282
				if (!$dnsPass) $this->_error(4);
283
				if (!$dnsHost) $this->_error(5);
284
				if (!$dnsDomain) $this->_error(5);
285
				if (!$dnsTTL) $this->_error(9);
286
				break;
287
			case 'freedns':
288
			case 'freedns-v6':
289
			case 'freedns2':
290
			case 'freedns2-v6':
291
				if (!$dnsHost) $this->_error(5);
292
				break;
293
			case 'gratisdns':
294
			case 'hover':
295
			case 'name.com':
296
			case 'name.com-v6':
297
				if (!$dnsUser) $this->_error(3);
298
				if (!$dnsPass) $this->_error(4);
299
				if (!$dnsHost) $this->_error(5);
300
				if (!$dnsDomain) $this->_error(5);
301
				break;
302
			case 'namecheap':
303
				if (!$dnsPass) $this->_error(4);
304
				if (!$dnsHost) $this->_error(5);
305
				if (!$dnsDomain) $this->_error(5);
306
				break;
307
			case 'route53':
308
			case 'route53-v6':
309
				if (!$dnsZoneID) $this->_error(8);
310
				if (!$dnsTTL) $this->_error(9);
311
				break;
312
			default:
313
				if (!$dnsUser) $this->_error(3);
314
				if (!$dnsPass) $this->_error(4);
315
				if (!$dnsHost) $this->_error(5);
316
			}
317

    
318
			switch ($dnsService) {
319
				case 'azurev6':
320
				case 'cloudflare-v6':
321
				case 'custom-v6':
322
				case 'desec-v6':
323
				case 'digitalocean-v6':
324
				case 'dnsimple-v6':
325
				case 'domeneshop-v6':
326
				case 'dreamhost-v6':
327
				case 'duiadns-v6':
328
				case 'dynv6-v6':
329
				case 'easydns-v6':
330
				case 'freedns-v6':
331
				case 'freedns2-v6':
332
				case 'gandi-livedns-v6':
333
				case 'godaddy-v6':
334
				case 'he-net-v6':
335
				case 'linode-v6':
336
				case 'mythicbeasts-v6':
337
				case 'name.com-v6':
338
				case 'noip-free-v6':
339
				case 'noip-v6':
340
				case 'route53-v6':
341
				case 'spdyn-v6':
342
				case 'yandex-v6':
343
					$this->_useIPv6 = true;
344
					break;
345
				default:
346
					$this->_useIPv6 = false;
347
			}
348
			$this->_dnsService = strtolower($dnsService);
349
			$this->_dnsUser = $dnsUser;
350
			$this->_dnsPass = base64_decode($dnsPass);
351
			$this->_dnsHost = $dnsHost;
352
			$this->_dnsDomain = $dnsDomain;
353
			$this->_dnsServer = $dnsServer;
354
			$this->_dnsPort = $dnsPort;
355
			$this->_dnsWildcard = $dnsWildcard;
356
			$this->_dnsProxied = $dnsProxied;
357
			$this->_dnsMX = $dnsMX;
358
			$this->_dnsZoneID = $dnsZoneID;
359
			$this->_dnsTTL = $dnsTTL;
360
			$this->_if = get_failover_interface($dnsIf);
361
			$this->_checkIP();
362
			$this->_dnsUpdateURL = $dnsUpdateURL;
363
			$this->_dnsResultMatch = $dnsResultMatch;
364
			if (($this->_dnsService == 'custom') || ($this->_dnsService == 'custom-v6')) {
365
				$this->_dnsRequestIf = get_failover_interface($dnsRequestIf);
366
			} else {
367
				$this->_dnsRequestIf = $this->_if;
368
			}
369
			if (($dnsMaxCacheAge !== null) && ($dnsMaxCacheAge !== "")) {
370
				$this->_dnsMaxCacheAgeDays = (int)$dnsMaxCacheAge;
371
			} else {
372
				switch ($dnsService) {
373
					// exceptions to _dnsMaxCacheAgeDays in an alphabetical order
374
					case 'dyfi':
375
						$this->_dnsMaxCacheAgeDays = 6;
376
						break;
377
					default:
378
						$this->_dnsMaxCacheAgeDays = 25;
379
				}
380
			}
381
			if ($this->_dnsVerboseLog) {
382
				log_error(sprintf(gettext('Dynamic DNS (%1$s): running get_failover_interface for %2$s. found %3$s'), $this->_FQDN, $dnsRequestIf, $this->_dnsRequestIf));
383
			}
384
			$this->_dnsRequestIfIP = get_interface_ip($dnsRequestIf);
385
			$this->_dnsDummyUpdateDone = false;
386
			$this->_forceUpdateNeeded = $forceUpdate;
387

    
388
			// Ensure that we were able to lookup the IP
389
			if (!is_ipaddr($this->_dnsIP)) {
390
				log_error(sprintf(gettext('Dynamic DNS (%1$s) There was an error trying to determine the public IP for interface - %2$s (%3$s %4$s).'), $this->_FQDN, $dnsIf, $this->_if, $this->_dnsIP));
391
				unlock($dyndnslck);
392
				return;
393
			}
394

    
395
			$this->_debugID = rand(1000000, 9999999);
396

    
397
			if ($forceUpdate == false && $this->_detectChange() == false) {
398
				$this->_error(10);
399
			} else {
400
				switch ($this->_dnsService) {
401
					case 'all-inkl':
402
					case 'azure':
403
					case 'azurev6':
404
					case 'citynetwork':
405
					case 'cloudflare':
406
					case 'cloudflare-v6':
407
					case 'cloudns':
408
					case 'custom':
409
					case 'custom-v6':
410
					case 'desec':
411
					case 'desec-v6':
412
					case 'dhs':
413
					case 'digitalocean':
414
					case 'digitalocean-v6':
415
					case 'dnsexit':
416
					case 'dnsimple':
417
					case 'dnsimple-v6':
418
					case 'dnsmadeeasy':
419
					case 'dnsomatic':
420
					case 'domeneshop':
421
					case 'domeneshop-v6':
422
					case 'duiadns':
423
					case 'duiadns-v6':
424
					case 'dyfi':
425
					case 'dyndns':
426
					case 'dyndns-custom':
427
					case 'dyndns-static':
428
					case 'dyns':
429
					case 'dynv6':
430
					case 'dynv6-v6':
431
					case 'easydns':
432
					case 'easydns-v6':
433
					case 'eurodns':
434
					case 'freedns':
435
					case 'freedns-v6':
436
					case 'freedns2':
437
					case 'freedns2-v6':
438
					case 'gandi-livedns':
439
					case 'gandi-livedns-v6':
440
					case 'glesys':
441
					case 'godaddy':
442
					case 'godaddy-v6':
443
					case 'googledomains':
444
					case 'gratisdns':
445
					case 'he-net':
446
					case 'he-net-tunnelbroker':
447
					case 'he-net-v6':
448
					case 'hn':
449
					case 'hover':
450
					case 'linode':
451
					case 'linode-v6':
452
					case 'loopia':
453
					case 'mythicbeasts':
454
					case 'mythicbeasts-v6':
455
					case 'name.com':
456
					case 'name.com-v6':
457
					case 'namecheap':
458
					case 'nicru':
459
					case 'nicru-v6':
460
					case 'noip':
461
					case 'noip-free':
462
					case 'noip-free-v6':
463
					case 'noip-v6':
464
					case 'ods':
465
					case 'opendns':
466
					case 'ovh-dynhost':
467
					case 'route53':
468
					case 'route53-v6':
469
					case 'selfhost':
470
					case 'spdyn':
471
					case 'spdyn-v6':
472
					case 'staticcling':
473
					case 'strato':
474
					case 'onecom':
475
					case 'onecom-v6':
476
					case 'yandex':
477
					case 'yandex-v6':
478
					case 'zoneedit':
479
						$this->_update();
480
						if ($this->_dnsDummyUpdateDone == true) {
481
							// If a dummy update was needed, then sleep a while and do the update again to put the proper address back.
482
							// Some providers (e.g. No-IP free accounts) need to have at least 1 address change every month.
483
							// If the address has not changed recently, or the user did "Force Update", then the code does
484
							// a dummy address change for providers like this.
485
							sleep(10);
486
							$this->_update();
487
						}
488
						break;
489
					case 'dreamhost':
490
					case 'dreamhost-v6':
491
						$this->_lookup_current();
492
						if (isset($this->status)) {
493
							return;
494
						}
495
						foreach ($this->_existingRecords as $record) {
496
							$this->_remove($record['existing_val']);
497
							$this->_update();
498
						}
499
						break;
500
					default:
501
						$this->_error(6);
502
						break;
503
				}
504
			}
505

    
506
			unlock($dyndnslck);
507
		}
508

    
509
		/*
510
		 * Private Function (added 12 July 05) [beta]
511
		 *   Send Update To Selected Service.
512
		 */
513
		function _update() {
514

    
515
			if ($this->_dnsVerboseLog) {
516
				log_error(sprintf(gettext('Dynamic DNS %1$s (%2$s): _update() starting.'), $this->_dnsService, $this->_FQDN));
517
			}
518

    
519
			if (strstr($this->_dnsRequestIf, "_vip")) {
520
				$parentif = get_configured_vip_interface($this->_dnsRequestIf);
521
				$realparentif = convert_friendly_interface_to_real_interface_name($parentif);
522
			} else {
523
				$realparentif = $this->_dnsRequestIf;
524
			}
525

    
526
			$ch = curl_init();
527

    
528
			if ($this->_useIPv6 == false) {
529
				curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
530
			}
531

    
532
			if ($this->_dnsService != 'ods') {
533
				curl_setopt($ch, CURLOPT_USERAGENT, $this->_UserAgent);
534
				curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
535
				curl_setopt($ch, CURLOPT_INTERFACE, 'if!' . $realparentif);
536
				curl_setopt($ch, CURLOPT_TIMEOUT, 120); // Completely empirical
537
			}
538

    
539
			switch ($this->_dnsService) {
540
				// The special custom provider
541
				case 'custom':
542
				case 'custom-v6':
543
					if (strstr($this->dnsUpdateURL, "%IP%")) {$needsIP = TRUE;} else {$needsIP = FALSE;}
544
					if ($this->_dnsUser != '') {
545
						if ($this->_curlIpresolveV4) {
546
							curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
547
						}
548
						if ($this->_curlSslVerifypeer) {
549
							/* required for a local CA
550
							 * see https://redmine.pfsense.org/issues/12589 */
551
							curl_setopt($ch, CURLOPT_CAPATH, "/etc/ssl/certs/");
552
							curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, TRUE);
553
						} else {
554
							curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
555
						}
556
						curl_setopt($ch, CURLOPT_USERPWD, "{$this->_dnsUser}:{$this->_dnsPass}");
557
					}
558
					$server = str_replace("%IP%", $this->_dnsIP, $this->_dnsUpdateURL);
559
					if ($this->_dnsVerboseLog) {
560
						log_error(sprintf(gettext("Sending request to: %s"), $server));
561
					}
562
					curl_setopt($ch, CURLOPT_URL, $server);
563
					break;
564
				// Providers in an alphabetical order. Add code for new providers below in a correct position.
565
				case 'dyfi':
566
					// see specification at https://www.dy.fi/page/specification
567
					$needsIP = FALSE;
568
					$server = 'https://www.dy.fi/nic/update';
569
					curl_setopt($ch, CURLOPT_USERPWD, "{$this->_dnsUser}:{$this->_dnsPass}");
570
					curl_setopt($ch, CURLOPT_URL, "{$server}?hostname={$this->_dnsHost}");
571
					break;
572
				case 'glesys':
573
					$needsIP = TRUE;
574
					$server = 'https://api.glesys.com/domain/updaterecord/format/json';
575
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
576
					$post_data['recordid'] = $this->_FQDN;
577
					$post_data['data'] = $this->_dnsIP;
578
					curl_setopt($ch, CURLOPT_URL, $server);
579
					curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
580
					break;
581
				//
582
				// Not yet ordered providers.
583
				// TODO: When editing a provider, move it above in a correct position.
584
				//
585
				case 'dyndns':
586
				case 'dyndns-static':
587
				case 'dyndns-custom':
588
					$needsIP = FALSE;
589
					if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") {
590
						$this->_dnsWildcard = "ON";
591
					}
592
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
593
					$server = "https://members.dyndns.org/nic/update";
594
					$port = "";
595
					if ($this->_dnsServer) {
596
						$server = $this->_dnsServer;
597
					}
598
					if ($this->_dnsPort) {
599
						$port = ":" . $this->_dnsPort;
600
					}
601
					curl_setopt($ch, CURLOPT_URL, $server . $port . '?system=dyndns&hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard=' . $this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NO');
602
					break;
603
				case 'dhs':
604
					// DHS is disabled in the GUI because the following doesn't work.
605
					$needsIP = TRUE;
606
					$post_data['hostscmd'] = 'edit';
607
					$post_data['hostscmdstage'] = '2';
608
					$post_data['type'] = '4';
609
					$post_data['updatetype'] = 'Online';
610
					$post_data['mx'] = $this->_dnsMX;
611
					$post_data['mx2'] = '';
612
					$post_data['txt'] = '';
613
					$post_data['offline_url'] = '';
614
					$post_data['cloak'] = 'Y';
615
					$post_data['cloak_title'] = '';
616
					$post_data['ip'] = $this->_dnsIP;
617
					$post_data['domain'] = 'dyn.dhs.org';
618
					$post_data['hostname'] = $this->_dnsHost;
619
					$post_data['submit'] = 'Update';
620
					$server = "https://members.dhs.org/nic/hosts";
621
					$port = "";
622
					if ($this->_dnsServer) {
623
						$server = $this->_dnsServer;
624
					}
625
					if ($this->_dnsPort) {
626
						$port = ":" . $this->_dnsPort;
627
					}
628
					curl_setopt($ch, CURLOPT_URL, $server . $port);
629
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
630
					curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
631
					break;
632
				case 'noip-v6':
633
				case 'noip-free-v6':
634
					$needsIP = TRUE;
635
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
636
					$server = "https://dynupdate.no-ip.com/nic/update";
637
					$port = "";
638
					if ($this->_dnsServer) {
639
						$server = $this->_dnsServer;
640
					}
641
					if ($this->_dnsPort) {
642
						$port = ":" . $this->_dnsPort;
643
					}
644
					if (($this->_dnsService == "noip-free-v6") &&
645
					    ($this->_forceUpdateNeeded == true) &&
646
					    ($this->_dnsDummyUpdateDone == false)) {
647
						// Update the IP to a dummy value to force No-IP free accounts to see a change.
648
						$iptoset = "fd00:d::1";
649
						$this->_dnsDummyUpdateDone = true;
650
						$log_message = 'Dynamic DNS %1$s (%2$s): ';
651
						$log_message .= 'Processing dummy update on No-IP free account. ';
652
						$log_message .= 'IP temporarily set to %3$s';
653
						log_error(sprintf(gettext($log_message), $this->_dnsService, $this->_dnsHost, $iptoset));
654
					} else {
655
						$iptoset = $this->_dnsIP;
656
					}
657
					curl_setopt($ch, CURLOPT_URL, $url_data);
658
					curl_setopt($ch, CURLOPT_URL, $server . $port . '?hostname=' . $this->_dnsHost . '&myipv6=' . $iptoset);
659
					break;
660
				case 'noip':
661
				case 'noip-free':
662
					$needsIP = TRUE;
663
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
664
					$server = "https://dynupdate.no-ip.com/nic/update";
665
					$port = "";
666
					if ($this->_dnsServer) {
667
						$server = $this->_dnsServer;
668
					}
669
					if ($this->_dnsPort) {
670
						$port = ":" . $this->_dnsPort;
671
					}
672
					if (($this->_dnsService == "noip-free") &&
673
					    ($this->_forceUpdateNeeded == true) &&
674
					    ($this->_dnsDummyUpdateDone == false)) {
675
						// Update the IP to a dummy value to force No-IP free accounts to see a change.
676
						$iptoset = "192.168.1.1";
677
						$this->_dnsDummyUpdateDone = true;
678
						log_error(sprintf(gettext('Dynamic DNS %1$s (%2$s): Processing dummy update on No-IP free account. IP temporarily set to %3$s'), $this->_dnsService, $this->_dnsHost, $iptoset));
679
					} else {
680
						$iptoset = $this->_dnsIP;
681
					}
682
					curl_setopt($ch, CURLOPT_URL, $server . $port . '?hostname=' . $this->_dnsHost . '&myip=' . $iptoset);
683
					break;
684
				case 'easydns':
685
					$needsIP = TRUE;
686
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
687
					$server = "https://members.easydns.com/dyn/dyndns.php";
688
					$port = "";
689
					if ($this->_dnsServer) {
690
						$server = $this->_dnsServer;
691
					}
692
					if ($this->_dnsPort) {
693
						$port = ":" . $this->_dnsPort;
694
					}
695
					curl_setopt($ch, CURLOPT_URL, $server . $port . '?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard=' . $this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=' . $this->_dnsBackMX);
696
					break;
697
				case 'easydns-v6':
698
					$needsIP = TRUE;
699
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
700
					$server = "https://members.easydns.com/dyn/dyndns.php";
701
					$port = "";
702
					if ($this->_dnsServer) {
703
						$server = $this->_dnsServer;
704
					}
705
					if ($this->_dnsPort) {
706
						$port = ":" . $this->_dnsPort;
707
					}
708
					curl_setopt($ch, CURLOPT_URL, $server . $port . '?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard=' . $this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=' . $this->_dnsBackMX);
709
					break;
710
				case 'hn':
711
					$needsIP = TRUE;
712
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
713
					$server = "http://dup.hn.org/vanity/update";
714
					$port = "";
715
					if ($this->_dnsServer) {
716
						$server = $this->_dnsServer;
717
					}
718
					if ($this->_dnsPort) {
719
						$port = ":" . $this->_dnsPort;
720
					}
721
					curl_setopt($ch, CURLOPT_URL, $server . $port . '?ver=1&IP=' . $this->_dnsIP);
722
					break;
723
				case 'zoneedit':
724
					$needsIP = FALSE;
725
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
726

    
727
					$server = "https://dynamic.zoneedit.com/auth/dynamic.html";
728
					$port = "";
729
					if ($this->_dnsServer) {
730
						$server = $this->_dnsServer;
731
					}
732
					if ($this->_dnsPort) {
733
						$port = ":" . $this->_dnsPort;
734
					}
735
					curl_setopt($ch, CURLOPT_URL, "{$server}{$port}?host=" . $this->_dnsHost . '&dnsto=' . $this->_dnsIP);
736
					break;
737
				case 'dyns':
738
					$needsIP = FALSE;
739
					$server = "http://www.dyns.net/postscript011.php";
740
					$port = "";
741
					if ($this->_dnsServer) {
742
						$server = $this->_dnsServer;
743
					}
744
					if ($this->_dnsPort) {
745
						$port = ":" . $this->_dnsPort;
746
					}
747
					curl_setopt($ch, CURLOPT_URL, $server . $port . '?username=' . urlencode($this->_dnsUser) . '&password=' . $this->_dnsPass . '&host=' . $this->_dnsHost);
748
					break;
749
				case 'ods':
750
					$needsIP = FALSE;
751
					$misc_errno = 0;
752
					$misc_error = "";
753
					$server = "ods.org";
754
					$port = "";
755
					if ($this->_dnsServer) {
756
						$server = $this->_dnsServer;
757
					}
758
					if ($this->_dnsPort) {
759
						$port = ":" . $this->_dnsPort;
760
					}
761
					$this->con['socket'] = fsockopen("{$server}{$port}", "7070", $misc_errno, $misc_error, 30);
762
					/* Check that we have connected */
763
					if (!$this->con['socket']) {
764
						print "error! could not connect.";
765
						break;
766
					}
767
					/* Here is the loop. Read the incoming data (from the socket connection) */
768
					while (!feof($this->con['socket'])) {
769
						$this->con['buffer']['all'] = trim(fgets($this->con['socket'], 4096));
770
						$code = substr($this->con['buffer']['all'], 0, 3);
771
						sleep(1);
772
						switch ($code) {
773
							case 100:
774
								fputs($this->con['socket'], "LOGIN " . $this->_dnsUser . " " . $this->_dnsPass . "\n");
775
								break;
776
							case 225:
777
								fputs($this->con['socket'], "DELRR " . $this->_dnsHost . " A\n");
778
								break;
779
							case 901:
780
								fputs($this->con['socket'], "ADDRR " . $this->_dnsHost . " A " . $this->_dnsIP . "\n");
781
								break;
782
							case 795:
783
								fputs($this->con['socket'], "QUIT\n");
784
								break;
785
						}
786
					}
787
					$this->_checkStatus(null, null, $code, null);
788
					break;
789
				case 'freedns':
790
				case 'freedns-v6':
791
					$needIP = TRUE;
792
					curl_setopt($ch, CURLOPT_URL, 'https://freedns.afraid.org/dynamic/update.php?' . $this->_dnsPass . '&address=' . $this->_dnsIP);
793
					break;
794
				case 'freedns2':
795
					$needIP = TRUE;
796
					curl_setopt($ch, CURLOPT_URL, 'https://sync.afraid.org/u/' . $this->_dnsPass . '/?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP);
797
					break;
798
				case 'freedns2-v6':
799
					$needIP = TRUE;
800
					curl_setopt($ch, CURLOPT_URL, 'https://v6.sync.afraid.org/u/' . $this->_dnsPass . '/?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP);
801
					break;
802
				case 'dnsexit':
803
					$needsIP = TRUE;
804
					curl_setopt($ch, CURLOPT_URL, 'https://api.dnsexit.com/dns/ud/?apikey=' . $this->_dnsPass . '&host=' . $this->_dnsHost . '&ip=' . $this->_dnsIP);
805
					break;
806
				case 'loopia':
807
					$needsIP = TRUE;
808
					if(isset($this->_dnsWildcard) && $this->_dnsWildcard == TRUE) {
809
						$this->_dnsWildcard = "ON";
810
					} else {
811
						$this->_dnsWildcard = "OFF";
812
					}
813
					curl_setopt($ch, CURLOPT_USERPWD, "{$this->_dnsUser}:{$this->_dnsPass}");
814
					curl_setopt($ch, CURLOPT_URL, "https://dyndns.loopia.se/?system=custom&hostname={$this->_dnsHost}&myip={$this->_dnsIP}&wildcard={$this->_dnsWildcard}&mx={$this->_dnsMX}&backmx=NO");
815
					break;
816
				case 'opendns':
817
					$needsIP = FALSE;
818
					if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") $this->_dnsWildcard = "ON";
819
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
820
					$server = "https://updates.opendns.com/nic/update?hostname=" . $this->_dnsHost;
821
					$port = "";
822
					if ($this->_dnsServer) {
823
						$server = $this->_dnsServer;
824
					}
825
					if ($this->_dnsPort) {
826
						$port = ":" . $this->_dnsPort;
827
					}
828
					curl_setopt($ch, CURLOPT_URL, $server . $port);
829
					break;
830

    
831
				case 'staticcling':
832
					$needsIP = FALSE;
833
					curl_setopt($ch, CURLOPT_URL, 'https://www.staticcling.org/update.html?login=' . $this->_dnsUser . '&pass=' . $this->_dnsPass);
834
					break;
835
				case 'dnsomatic':
836
					/* Example syntax
837
						https://username:password@updates.dnsomatic.com/nic/update?hostname=yourhostname&myip=ipaddress&wildcard=NOCHG&mx=NOCHG&backmx=NOCHG
838
					*/
839
					$needsIP = FALSE;
840
					if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") {
841
						$this->_dnsWildcard = "ON";
842
					}
843
					/*
844
					Reference: https://www.dnsomatic.com/docs/api
845
						DNS-O-Matic usernames are 3-25 characters.
846
						DNS-O-Matic passwords are 6-20 characters.
847
						All ASCII letters and numbers accepted.
848
						Dots, dashes, and underscores allowed, but not at the beginning or end of the string.
849
					Required: "rawurlencode" https://www.php.net/manual/en/function.rawurlencode.php
850
						Encodes the given string according to RFC 3986.
851
					*/
852
					$server = "https://" . rawurlencode($this->_dnsUser) . ":" . rawurlencode($this->_dnsPass) . "@updates.dnsomatic.com/nic/update?hostname=";
853
					if ($this->_dnsServer) {
854
						$server = $this->_dnsServer;
855
					}
856
					if ($this->_dnsPort) {
857
						$port = ":" . $this->_dnsPort;
858
					}
859
					curl_setopt($ch, CURLOPT_URL, $server . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard=' . $this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NOCHG');
860
					break;
861
				case 'domeneshop':
862
				case 'domeneshop-v6':
863
					/* Example:
864
						https://{token}:{secret}@api.domeneshop.no/v0/dyndns/update?hostname=example.com&myip=127.0.0.1
865
					*/
866
					$needsIP = FALSE;
867
					$server = "https://{$this->_dnsUser}:{$this->_dnsPass}@api.domeneshop.no/v0/dyndns/update?hostname={$this->_dnsHost}&myip={$this->_dnsIP}";
868
					curl_setopt($ch, CURLOPT_URL, $server);
869
					break;
870
				case 'mythicbeasts':
871
				case 'mythicbeasts-v6':
872
					/* Example:
873
						https://{login}:{password}@https://api.mythic-beasts.com/dns/v2/zones/example.com/records/test1/A -d data=1.2.3.4
874
					*/
875
					$needsIP = FALSE;
876
					if ($this->_useIPv6) {
877
						$record = "AAAA";
878
					} else {
879
						$record = "A";
880
					}
881
					$post_data['data'] = $this->_dnsIP;
882
					$server = "https://api.mythic-beasts.com/dns/v2/zones/{$this->_dnsDomain}/records/{$this->_dnsHost}/{$record}";
883
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
884
					curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
885
					curl_setopt($ch, CURLOPT_URL, $server);
886
					break;
887
				case 'name.com':
888
				case 'name.com-v6':
889
					// API documentation: https://www.name.com/api-docs/ & https://www.name.com/api-docs/dns
890
					$namedotcom_api = "https://api.name.com/v4/domains/{$this->_dnsDomain}/records";
891
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
892
					$record_type = $this->_useIPv6 ? "AAAA" : "A";
893
					// Check if a record already exists for this host.
894
					curl_setopt($ch, CURLOPT_URL, "{$namedotcom_api}?perPage=1000");
895
					curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
896
					$response = json_decode(curl_exec($ch), true);
897
					$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
898
					if ($http_code != "200") {
899
						log_error(gettext("Error message: ") . $response);
900
						return false;
901
					}
902
					if (!is_array($response["records"])) {
903
						log_error(gettext("Unexpected response: ") . $response);
904
						return false;
905
					}
906
					foreach($response["records"] as $record) {
907
						if (($record["domainName"] == $this->_dnsDomain) &&
908
							($record["host"] == $this->_dnsHost) &&
909
							($record["type"] == $record_type)) {
910
								$record_id = $record["id"];
911
								$existing_ttl = $record["ttl"];
912
								break;
913
						}
914
					}
915
					if (!$record_id && $response["nextPage"])
916
					{
917
						log_error(gettext("Too many (>1000) Name.com DNS records. Paging not supported."));
918
						return false;
919
					}
920
					// Either update an existing record or create a new one.
921
					$post_data['host'] = $this->_dnsHost;
922
					$post_data['type'] = $record_type;
923
					$post_data['answer'] = $this->_dnsIP;
924
					$post_data['ttl'] = max($this->_dnsTTL ?: $existing_ttl, 300);
925
					curl_setopt($ch, CURLOPT_URL, "{$namedotcom_api}/{$record_id}");
926
					curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
927
					curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
928
					curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $record_id ? "PUT" : "POST");
929
					break;
930
				case 'namecheap':
931
					/* Example:
932
						https://dynamicdns.park-your-domain.com/update?host=[host_name]&domain=[domain.com]&password=[domain_password]&ip=[your_ip]
933
					*/
934
					$needsIP = FALSE;
935
					$dnspass = trim($this->_dnsPass);
936
					$server = "https://dynamicdns.park-your-domain.com/update?host={$this->_dnsHost}&domain={$this->_dnsDomain}&password={$dnspass}&ip={$this->_dnsIP}";
937
					curl_setopt($ch, CURLOPT_URL, $server);
938
					break;
939
				case 'nicru':
940
				case 'nicru-v6':
941
					/* see https://www.nic.ru/help/dynamic-dns-for-developers_5810.html */
942
					$needsIP = FALSE;
943
					if (is_ipaddrv6($this->_dnsIP)) {
944
						$iptype = "ipv6";
945
					} else {
946
						$iptype = "myip";
947
					}
948
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
949
					$server = "https://api.nic.ru/dyndns/update?hostname={$this->_dnsHost}&{$iptype}={$this->_dnsIP}";
950
					curl_setopt($ch, CURLOPT_URL, $server);
951
					break;
952
				case 'yandex':
953
				case 'yandex-v6':
954
					// https://yandex.com/dev/connect/directory/api/concepts/domains/dns-records-via-pdd.html
955

    
956
					if (is_ipaddrv4($this->_dnsIP)) {
957
						$type = 'A';
958
					} else {
959
						$type = 'AAAA';
960
					}
961

    
962
					// get record_id
963
					curl_setopt($ch, CURLOPT_URL, "https://pddimp.yandex.ru/api2/admin/dns/list?domain={$this->_dnsDomain}");
964
					curl_setopt($ch, CURLOPT_HTTPHEADER, array('PddToken: ' . $this->_dnsPass));
965
					curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
966
					$output = json_decode(curl_exec($ch), true);
967
					if (is_array($output["records"])) {
968
						foreach($output["records"] as $record) {
969
							if (($record["domain"] == $this->_dnsDomain) &&
970
							    ($record["subdomain"] == $this->_dnsHost) &&
971
							    ($record["type"] == $type)) {
972
								$record_id = $record["record_id"];
973
							}
974
						}
975
					}
976

    
977
					if ($record_id) {
978
						$action = 'edit';
979
						$post_data['record_id'] = $record_id;
980
					} else {
981
						$action = 'add';
982
					}
983

    
984
					$post_data['domain'] = $this->_dnsDomain;
985
					$post_data['subdomain'] = $this->_dnsHost;
986
					$post_data['content'] = $this->_dnsIP;
987
					$post_data['type'] = $type;
988
					if ($this->_dnsTTL) {
989
						$post_data['ttl'] = $this->_dnsTTL;
990
					}
991

    
992
					curl_setopt($ch, CURLOPT_URL, 'https://pddimp.yandex.ru/api2/admin/dns/' . $action);
993
					curl_setopt($ch, CURLOPT_HTTPHEADER, array('PddToken: ' . $this->_dnsPass));
994
					curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
995
					break;
996
				case 'duiadns':
997
				case 'duiadns-v6':
998
					$needsIP = FALSE;
999
					$server = "https://ipv4.duiadns.net/dyndns.duia?";
1000
					curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
1001
					curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
1002
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
1003
					curl_setopt($ch, CURLOPT_URL, $server . 'hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP);
1004
					break;
1005
				case 'he-net':
1006
				case 'he-net-v6':
1007
					$needsIP = FALSE;
1008
					$server = "https://dyn.dns.he.net/nic/update?";
1009
					curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
1010
					curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
1011
					curl_setopt($ch, CURLOPT_URL, $server . 'hostname=' . $this->_dnsHost . '&password=' . $this->_dnsPass . '&myip=' . $this->_dnsIP);
1012
					break;
1013
				case 'he-net-tunnelbroker':
1014
					$needsIP = FALSE;
1015
					$server = "https://ipv4.tunnelbroker.net/nic/update?";
1016
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
1017
					curl_setopt($ch, CURLOPT_URL, $server . 'hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP);
1018
					break;
1019
				case 'selfhost':
1020
					$needsIP = FALSE;
1021
					if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") {
1022
						$this->_dnsWildcard = "ON";
1023
					}
1024
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
1025
					$server = "https://carol.selfhost.de/nic/update";
1026
					$port = "";
1027
					if ($this->_dnsServer) {
1028
						$server = $this->_dnsServer;
1029
					}
1030
					if ($this->_dnsPort) {
1031
						$port = ":" . $this->_dnsPort;
1032
					}
1033
					curl_setopt($ch, CURLOPT_URL, $server . $port . '?system=dyndns&hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard=' . $this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NO');
1034
					break;
1035
				case 'strato':
1036
					$needsIP = FALSE;
1037
					$server = 'https://dyndns.strato.com/nic/update';
1038
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
1039
					curl_setopt($ch, CURLOPT_URL, $server . '?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP);
1040
					break;
1041
				case 'route53':
1042
					require_once("r53.class");
1043
					$r53 = new Route53($this->_dnsUser, $this->_dnsPass);
1044
					$apiurl = $r53->getApiUrl($this->_dnsZoneID);
1045
					$xmlreq = $r53->getRequestBody($this->_dnsHost, $this->_dnsIP, $this->_dnsTTL);
1046
					$httphead = $r53->getHttpPostHeaders($this->_dnsZoneID, "us-east-1", hash("sha256",$xmlreq));
1047
					curl_setopt($ch, CURLOPT_HTTPHEADER, $httphead);
1048
					if($this->_dnsVerboseLog){
1049
						log_error(sprintf("Sending request to: %s", $apiurl));
1050
						foreach($httphead as $hv){
1051
							log_error(sprintf("Header: %s", $hv));
1052
						}
1053
						log_error(sprintf("XMLPOST: %s", $xmlreq));
1054
					}
1055
					curl_setopt($ch, CURLOPT_URL, $apiurl);
1056
					curl_setopt($ch, CURLOPT_POSTFIELDS, $xmlreq);
1057
					break;
1058
				case 'route53-v6':
1059
					require_once("r53.class");
1060
					$r53 = new Route53($this->_dnsUser, $this->_dnsPass);
1061
					$apiurl = $r53->getApiUrl($this->_dnsZoneID);
1062
					$xmlreq = $r53->getRequestBodyV6($this->_dnsHost, $this->_dnsIP, $this->_dnsTTL);
1063
					$httphead = $r53->getHttpPostHeaders($this->_dnsZoneID, "us-east-1", hash("sha256",$xmlreq));
1064
					curl_setopt($ch, CURLOPT_HTTPHEADER, $httphead);
1065
					if($this->_dnsVerboseLog){
1066
						log_error(sprintf("Sending request to: %s", $apiurl));
1067
						foreach($httphead as $hv){
1068
							log_error(sprintf("Header: %s", $hv));
1069
						}
1070
						log_error(sprintf("XMLPOST: %s", $xmlreq));
1071
					}
1072
					curl_setopt($ch, CURLOPT_URL, $apiurl);
1073
					curl_setopt($ch, CURLOPT_POSTFIELDS, $xmlreq);
1074
					break;
1075
				case 'cloudflare-v6':
1076
				case 'cloudflare':
1077
					$this->_FQDN = ltrim($this->_FQDN, '@.');
1078
					$isv6 = ($this->_dnsService === 'cloudflare-v6');
1079
					$recordType = $isv6 ? "AAAA" : "A";
1080
					$needsIP = TRUE;
1081
					$dnsServer ='api.cloudflare.com';
1082
					$dnsHost = str_replace(' ', '', $this->_dnsHost);
1083

    
1084
					curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1085

    
1086
					if ((!$this->_dnsUser) || (strpos($this->_dnsUser, '@') !== false)) {
1087
						if (strpos($this->_dnsUser, '@') !== false) {
1088
							curl_setopt($ch, CURLOPT_HTTPHEADER, array(
1089
										'X-Auth-Email: ' . $this->_dnsUser,
1090
										'X-Auth-Key: ' . $this->_dnsPass,
1091
										'Content-Type: application/json'
1092
										));
1093
						} else {
1094
							curl_setopt($ch, CURLOPT_HTTPHEADER, array(
1095
										'Authorization: Bearer ' . $this->_dnsPass,
1096
										'Content-Type: application/json'
1097
										));
1098
						}
1099

    
1100
						// Get zone ID
1101
						$getZoneId = "https://{$dnsServer}/client/v4/zones/?name={$this->_dnsDomain}";
1102
						curl_setopt($ch, CURLOPT_URL, $getZoneId);
1103
						curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1104
						$output = json_decode(curl_exec($ch));
1105
						$zone = $output->result[0]->id;
1106
					} else {
1107
						curl_setopt($ch, CURLOPT_HTTPHEADER, array(
1108
							'Authorization: Bearer ' . $this->_dnsPass,
1109
							'Content-Type: application/json'
1110
						));
1111

    
1112
						$zone = $this->_dnsUser;
1113
					}
1114

    
1115
					if ($zone) { // If zone ID was found get host ID
1116
						$getHostId = "https://{$dnsServer}/client/v4/zones/{$zone}/dns_records?name={$this->_FQDN}&type={$recordType}";
1117
						curl_setopt($ch, CURLOPT_URL, $getHostId);
1118
						curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1119
						$output = json_decode(curl_exec($ch));
1120
						$host = $output->result[0]->id;
1121
						if ($host) { // If host ID was found update host
1122
							$hostData = array(
1123
								"content" => "{$this->_dnsIP}",
1124
								"type" => "{$recordType}",
1125
								"proxied" => $this->_dnsProxied,
1126
								"name" => "{$this->_dnsHost}",
1127
								"ttl" => empty($this->_dnsTTL) ? 1 : (int) $this->_dnsTTL
1128
							);
1129
							$data_json = json_encode($hostData);
1130
							$updateHostId = "https://{$dnsServer}/client/v4/zones/{$zone}/dns_records/{$host}";
1131
							curl_setopt($ch, CURLOPT_URL, $updateHostId);
1132
							curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
1133
							curl_setopt($ch, CURLOPT_POSTFIELDS, $data_json);
1134
						}
1135
					}
1136
					break;
1137
				case 'eurodns':
1138
					$needsIP = TRUE;
1139
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
1140
					$server = "https://update.eurodyndns.org/update/";
1141
					$port = "";
1142
					if ($this->_dnsPort) {
1143
						$port = ":" . $this->_dnsPort;
1144
					}
1145
					curl_setopt($ch, CURLOPT_URL, $server . $port . '?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP);
1146
					break;
1147
				case 'gratisdns':
1148
					$needsIP = TRUE;
1149
					$server = "https://ssl.gratisdns.dk/ddns.phtml";
1150
					curl_setopt($ch, CURLOPT_URL, $server . '?u=' . $this->_dnsUser . '&p=' . $this->_dnsPass . '&h=' . $this->_dnsHost . '&d=' . $this->_dnsDomain . '&i=' . $this->_dnsIP);
1151
					break;
1152
				case 'ovh-dynhost':
1153
					$needsIP = FALSE;
1154
					if (isset($this->_dnsWildcard) && $this->_dnsWildcard != "OFF") $this->_dnsWildcard = "ON";
1155
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
1156
					$server = "https://www.ovh.com/nic/update";
1157
					$port = "";
1158
					if ($this->_dnsServer) {
1159
						$server = $this->_dnsServer;
1160
					}
1161
					if ($this->_dnsPort) {
1162
						$port = ":" . $this->_dnsPort;
1163
					}
1164
					curl_setopt($ch, CURLOPT_URL, $server . $port . '?system=dyndns&hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP . '&wildcard=' . $this->_dnsWildcard . '&mx=' . $this->_dnsMX . '&backmx=NO');
1165
					break;
1166
				case 'citynetwork':
1167
					$needsIP = TRUE;
1168
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
1169
					$server = 'https://dyndns.citynetwork.se/nic/update';
1170
					$port = "";
1171
					if ($this->_dnsServer) {
1172
						$server = $this->_dnsServer;
1173
					}
1174
					if ($this->_dnsPort) {
1175
						$port = ":" . $this->_dnsPort;
1176
					}
1177
					curl_setopt($ch, CURLOPT_URL, $server . $port . '?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP);
1178
					break;
1179
				case 'dnsimple':
1180
				case 'dnsimple-v6':
1181
					/* Uses DNSimple's v2 REST API
1182
					   Requires the Account ID as the username (found in the URL when pull up the domain)
1183
					   And an API Token for the password (generated in the User Settings -> API tokens area of the website)
1184
					   Piggybacks on Route 53's ZoneID field for the DNSimple record ID to update
1185
					   The DNS record MUST exist before it can update since it performs a PATCH operation
1186
					   Data sent as JSON over HTTPS */
1187
					$needsIP = TRUE;
1188
					$server = 'https://api.dnsimple.com/v2/';
1189
					curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
1190
					curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: application/json', 'Content-Type: application/json', 'Authorization: Bearer ' . $this->_dnsPass));
1191
					curl_setopt($ch, CURLOPT_URL, $server . $this->_dnsUser . '/zones/' . $this->_dnsHost . '/records/' . $this->_dnsZoneID);
1192
					curl_setopt($ch, CURLOPT_POSTFIELDS, '{"content":"' . $this->_dnsIP . '","ttl":"' . $this->_dnsTTL . '"}');
1193
					break;
1194
				case 'godaddy':
1195
				case 'godaddy-v6':
1196
					/* Uses GoDaddy's REST API
1197
					   Requires username and Account API sso-key passed in header
1198
					   Data sent as JSON */
1199
					$needsIP = TRUE;
1200
					$server = 'https://api.godaddy.com/v1/domains/';
1201
					$recordType = $this->_useIPv6 ? "AAAA" : "A";
1202
					$url = $server . $this->_dnsDomain . '/records/' . $recordType . '/' . $this->_dnsHost;
1203
					$jsondata = '[{"data":"' . $this->_dnsIP . '","ttl":' . $this->_dnsTTL . '}]';
1204
					curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
1205
					curl_setopt($ch, CURLOPT_HTTPHEADER, array(
1206
						'Accept: application/json',
1207
						'Content-Type: application/json',
1208
						'Authorization: sso-key ' . $this->_dnsUser . ':' . $this->_dnsPass
1209
					));
1210
					curl_setopt($ch, CURLOPT_URL, $url);
1211
					curl_setopt($ch, CURLOPT_POSTFIELDS, $jsondata);
1212
					break;
1213
				case 'googledomains':
1214
					$needsIP = FALSE;
1215
					$post_data['username:password'] = $this->_dnsUser . ':' . $this->_dnsPass;
1216
					$post_data['hostname'] = $this->_dnsHost;
1217
					$post_data['myip'] = $this->_dnsIP;
1218
					$post_data['offline'] = 'no';
1219
					$server = "https://domains.google.com/nic/update";
1220
					$port = "";
1221
					curl_setopt($ch, CURLOPT_URL, 'https://domains.google.com/nic/update');
1222
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
1223
					curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
1224
					curl_setopt($ch, CURLOPT_USERAGENT, $this->_UserAgent);
1225
					curl_setopt($ch, CURLOPT_POST, 1);
1226
					break;
1227
				case 'dnsmadeeasy':
1228
					$needsIP = TRUE;
1229
					$server = "https://cp.dnsmadeeasy.com/servlet/updateip";
1230
					$username = empty($this->_dnsUser) ? "" : "&username={$this->dnsUser}";
1231
					curl_setopt($ch, CURLOPT_URL, $server . '?password=' . $this->_dnsPass . '&id=' . $this->_dnsHost . '&ip=' . $this->_dnsIP . $username);
1232
					break;
1233
				case 'spdyn':
1234
				case 'spdyn-v6':
1235
					$needsIP = FALSE;
1236
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
1237
					$server = "https://update.spdyn.de/nic/update";
1238
					$port = "";
1239
					if ($this->_dnsServer) {
1240
						$server = $this->_dnsServer;
1241
					}
1242
					if ($this->_dnsPort) {
1243
						$port = ":" . $this->_dnsPort;
1244
					}
1245
					curl_setopt($ch, CURLOPT_URL, $server . $port . '?hostname=' . $this->_dnsHost . '&myip=' . $this->_dnsIP);
1246
					break;
1247
				case 'all-inkl':
1248
					$needsIP = FALSE;
1249
					$server = 'https://dyndns.kasserver.com/';
1250
					curl_setopt($ch, CURLOPT_USERPWD, $this->_dnsUser . ':' . $this->_dnsPass);
1251
					curl_setopt($ch, CURLOPT_URL, $server . 'myip=' . $this->_dnsIP);
1252
					break;
1253
				case 'hover':
1254
					$needsIP = FALSE;
1255
					$port = "";
1256
					curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1257
					curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
1258

    
1259
					//step 1: login to API
1260
					$post_data['username'] = $this->_dnsUser;
1261
					$post_data['password'] = $this->_dnsPass;
1262
					curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
1263
					curl_setopt($ch, CURLOPT_URL, "https://www.hover.com/api/login");
1264
					curl_setopt($ch, CURLOPT_HEADER, 1); //return the full headers to extract the cookies
1265
					curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1266
					$output = curl_exec($ch);
1267

    
1268
					//extract the cookies
1269
					preg_match_all("/^Set-cookie: (.*?);/ism", $output, $cookies);
1270
					if( count($cookies[1]) > 0 ){
1271
						$cookie_data = implode("; ",$cookies[1]);
1272
					}
1273

    
1274
					//step 2: find the id of the A record
1275
					$post_data = null;
1276
					curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
1277
					curl_setopt($ch, CURLOPT_COOKIE, $cookie_data);
1278
					curl_setopt($ch, CURLOPT_HEADER, 0);
1279
					curl_setopt($ch, CURLOPT_URL, "https://www.hover.com/api/dns");
1280
					curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
1281
					curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1282
					$output = curl_exec($ch);
1283
					$pregHost = preg_quote($this->_dnsHost);
1284
					$pregDomain = preg_quote($this->_dnsDomain);
1285
					preg_match("/^{\"succeeded\":true.*?domain_name\":\"{$pregDomain}.*?entries.*?{\"id\":\"([^\"]*?)\",\"name\":\"{$pregHost}\",\"type\":\"A\".*?\$/", $output, $hostID);
1286
					$hostID = $hostID[1];
1287
					preg_match("/^{\"succeeded\":true.*?domain_name\":\"{$pregDomain}.*?entries.*?{[^\}]*?\"name\":\"{$pregHost}\",\"type\":\"A\".*?content\":\"([^\"]*?)\".*?\$/", $output, $hostIP);
1288
					$hostIP = $hostIP[1];
1289
					unset($pregHost);
1290
					unset($pregDomain);
1291

    
1292
					//step 3: update the IP
1293
					if ($hostID) {
1294
						curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1295
						curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
1296
						curl_setopt($ch, CURLOPT_COOKIE, $cookie_data);
1297
						$post_data['content'] = $this->_dnsIP;
1298
						curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
1299
						curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
1300
						curl_setopt($ch, CURLOPT_URL, "https://www.hover.com/api/dns/{$hostID}");
1301
						log_error("HostID:{$hostID}, OldIP:{$hostIP}");
1302
					}
1303
					break;
1304
				case 'onecom':
1305
				case 'onecom-v6':
1306
					/* see https://redmine.pfsense.org/issues/11293
1307
					 * and https://redmine.pfsense.org/issues/12352 */
1308

    
1309
					curl_setopt($ch, CURLOPT_URL, "https://www.one.com/admin/");
1310
					curl_setopt($ch, CURLOPT_HEADER, 1); //return the full headers to extract the cookies
1311
					curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
1312
					curl_setopt($ch, CURLOPT_USERAGENT, $this->_UserAgent);
1313
					curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1314
					$output = curl_exec($ch);
1315
					$last_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
1316

    
1317
					// extract the cookies
1318
					preg_match_all("/^Set-cookie: (.*?);/ism", $output, $cookies);
1319
					if (count($cookies[1]) > 0) {
1320
						$cookie_data = implode("; ", $cookies[1]);
1321
					}
1322

    
1323
					// login in
1324
					$post_data['username'] = $this->_dnsUser;
1325
					$post_data['password'] = $this->_dnsPass;
1326
					$post_data['credentialId'] = '';
1327

    
1328
					curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
1329
					curl_setopt($ch, CURLOPT_URL, $last_url);
1330
					curl_setopt($ch, CURLOPT_COOKIE, $cookie_data);
1331
					curl_setopt($ch, CURLOPT_HEADER, 1); //return the full headers to extract the cookies
1332
					curl_setopt($ch, CURLOPT_USERAGENT, $this->_UserAgent);
1333
					curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1334
					$output = curl_exec($ch);
1335

    
1336
					// extract the cookies
1337
					preg_match_all("/^Set-cookie: (.*?);/ism", $output, $cookies);
1338
					if (count($cookies[1]) > 0) {
1339
						$cookie_data = implode("; ", $cookies[1]);
1340
					}
1341

    
1342
					// gets all DNS records of the domain.
1343
					$post_data = null;
1344
					$url = "https://www.one.com/admin/api/domains/" . $this->_dnsDomain + "/dns/custom_records";
1345
					curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
1346
					curl_setopt($ch, CURLOPT_COOKIE, $cookie_data);
1347
					curl_setopt($ch, CURLOPT_HEADER, 0);
1348
					curl_setopt($ch, CURLOPT_URL, $url);
1349
					curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
1350
					curl_setopt($ch, CURLOPT_USERAGENT, $this->_UserAgent);
1351
					curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1352
					$output = curl_exec($ch);
1353
					$result = json_decode($output, true);
1354
					$records = $result['result']['data'];
1355

    
1356
					// finds the record id of a record from it's subdomain
1357
					foreach ($records as $rec) {
1358
						if ($rec['attributes']['prefix'] == $this->_dnsHostname) {
1359
							$id = $rec['id'];
1360
							break;
1361
						}
1362
					}
1363
					if (!$id) {
1364
						log_error("Could not find one.com hostname record id");
1365
						return false;
1366
					}
1367

    
1368
					// changes the IP Address of a TYPE A record. Default TTL=3800
1369
					$tosend = array();
1370
					$tosend['type'] = 'dns_service_records';
1371
					$tosend['id'] = $id;
1372
					if (is_ipaddrv4($this->_dnsIP)) {
1373
						$rectype = 'A';
1374
					} else {
1375
						$rectype = 'AAAA';
1376
					}
1377
					$tosend['attributes'] = array(
1378
						'type' => $rectype,
1379
						'prefix' => $this->_dnsHostname,
1380
						'content' => $this->_dnsIP,
1381
						'ttl' => $this->_dnsTTL
1382
					);
1383
					$url = "https://www.one.com/admin/api/domains/" . $this->_dnsDomain .
1384
						 "/dns/custom_records/" . $id;
1385
					curl_setopt($ch, CURLOPT_URL, $url);
1386
					curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/json"));
1387
					curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($tosend));
1388
					curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
1389
					curl_setopt($ch, CURLOPT_USERAGENT, $this->_UserAgent);
1390
					break;
1391
				case 'dreamhost':
1392
				case 'dreamhost-v6':
1393
					$needsIP = TRUE;
1394
					$isv6 = ($this->_dnsService === 'dreamhost-v6');
1395
					$server = 'https://api.dreamhost.com/';
1396
					$post_data['key'] = $this->_dnsPass;
1397
					$post_data['unique_id'] = uniqid($this->_dnsHost);
1398
					$post_data['cmd'] = 'dns-add_record';
1399
					$post_data['format'] = 'json';
1400
					$post_data['value'] = $this->_dnsIP;
1401
					$post_data['record'] = $this->_dnsHost;
1402
					$post_data['type'] = $isv6 ? 'AAAA' : 'A';
1403
					$post_data['comment'] = "Updated by pfSense:$this->_dnsUser on " . date('c');
1404
					$port = "";
1405
					if ($this->_dnsServer) {
1406
						$server = $this->_dnsServer;
1407
					}
1408
					if ($this->_dnsPort) {
1409
						$port = ":" . $this->_dnsPort;
1410
					}
1411
					curl_setopt($ch, CURLOPT_URL, $server . $port);
1412
					curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
1413
					break;
1414
				case 'digitalocean':
1415
				case 'digitalocean-v6':
1416
					// Get record ID
1417
					$server = 'https://api.digitalocean.com/v2/domains/';
1418
					$isv6 = ($this->_dnsService === 'digitalocean-v6');
1419
					$url = $server . $this->_dnsDomain . '/records';
1420
					curl_setopt($ch, CURLOPT_HTTPHEADER, array("Authorization: Bearer {$this->_dnsPass}"));
1421
					curl_setopt($ch, CURLOPT_URL, $url);
1422
					curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1423
					$output = json_decode(curl_exec($ch));
1424
					if (!is_array($output->domain_records)) {
1425
						$output->domain_records = array();
1426
					}
1427

    
1428
					// DO's API lists 20 NS records per page, so additional pages needs to be downloaded
1429
					// https://redmine.pfsense.org/issues/10952
1430
					$_domain_records = $output->domain_records;
1431
					$_count = count($_domain_records);
1432
					$_total = 0;
1433
					if (property_exists($output, 'meta')) {
1434
						$meta = $output->meta;
1435
						if (property_exists($meta, 'total')) {
1436
							$_total = $meta->total;
1437
						}
1438
					}
1439
					$_next = '...';
1440
					$_last = '';
1441
					while ($_next != $_last) {
1442
						$_next = '';
1443
						if (property_exists($output, 'links')) {
1444
							$_links = $output->links;
1445
							if (property_exists($_links, 'pages')) {
1446
								$_pages = $_links->pages;
1447
								if (property_exists($_pages, 'next')) {
1448
									$_next = $_pages->next;
1449
								}
1450
								if (property_exists($_pages, 'last')) {
1451
									$_last = $_pages->last;
1452
								}
1453
								if ($_next != '') {
1454
									echo "getting $_next\n";
1455
									curl_setopt($ch, CURLOPT_URL, $_next);
1456
									curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1457
									$output = json_decode(curl_exec($ch));
1458
									if (!is_array($output->domain_records)) {
1459
										$output->domain_records = array();
1460
									}
1461
									$_domain_records = array_merge($_domain_records,$output->domain_records);
1462
								}
1463
							}
1464
						}
1465
					}
1466
					$_count = count($_domain_records);
1467

    
1468
					foreach($_domain_records as $dnsRecord) {
1469
						// NS records are named @ in DO's API, so check type as well
1470
						// https://redmine.pfsense.org/issues/9171
1471
						if ($this->_dnsHost == $dnsRecord->name && $dnsRecord->type == ($isv6 ? 'AAAA' : 'A')) {
1472
							$recordID = $dnsRecord->id;
1473
							break;
1474
						}
1475
					}
1476

    
1477
					// Create/update record
1478
					if ($recordID == null) {
1479
						$url = $server . $this->_dnsDomain . '/records';
1480
					} else {
1481
						$url = $server . $this->_dnsDomain . '/records/' . $recordID;
1482
						curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
1483
					}
1484
					$post_data['type'] = $isv6 ? 'AAAA' : 'A';
1485
					$post_data['ttl'] = $this->_dnsTTL;
1486
					$post_data['name'] = $this->_dnsHost;
1487
					$post_data['data'] = $this->_dnsIP;
1488
					curl_setopt($ch, CURLOPT_URL, $url);
1489
					curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
1490
					break;
1491
				case 'cloudns':
1492
					/* Uses ClouDNS REST API
1493
					   Requires auth-id or sub-auth-id or sub-auth-user */
1494
					// Step 1: Find the Record ID
1495
					$url = 'https://api.cloudns.net/dns/records.json';
1496
					$post_data['auth-id'] = $this->_dnsUser;
1497
					$post_data['auth-password'] = $this->_dnsPass;
1498
					$post_data['domain-name'] = $this->_dnsDomain;
1499
					$post_data['host'] = $this->_dnsHost;
1500
					$post_data['type'] = 'a';
1501
					curl_setopt($ch, CURLOPT_URL, $url);
1502
					curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
1503
					curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1504
					$output = json_decode(curl_exec($ch));
1505
					$recordID = key(get_object_vars($output));
1506

    
1507
					// Step 2: Set the record
1508
					$needsIP = TRUE;
1509
					$url = 'https://api.cloudns.net/dns/mod-record.json';
1510
					$post_data = array();
1511
					$post_data['auth-id'] = $this->_dnsUser;
1512
					$post_data['auth-password'] = $this->_dnsPass;
1513
					$post_data['domain-name'] = $this->_dnsDomain;
1514
					$post_data['record-id'] = $recordID;
1515
					$post_data['host'] = $this->_dnsHost;
1516
					$post_data['record'] = $this->_dnsIP;
1517
					$post_data['ttl'] = $this->_dnsTTL;
1518
					curl_setopt($ch, CURLOPT_URL, $url);
1519
					curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
1520
					break;
1521
				case 'azurev6':
1522
				case 'azure':
1523
					$hostname = "{$this->_dnsHost}";
1524
					$resourceid = trim($this->_dnsZoneID);
1525
					$app_id = $this->_dnsUser;
1526
					$client_secret = $this->_dnsPass;
1527
					$newip = $this->_dnsIP;
1528
					$newttl = $this->_dnsTTL;
1529
						// ensure resourceid starts with / and has no trailing /
1530
					$resourceid = '/' . trim($resourceid, '/');
1531
						// extract subscription id from resource id
1532
					preg_match('/\\/subscriptions\\/(?<sid>[^\\/]*)/', $resourceid, $result);
1533
					$subscriptionid = isset($result['sid']) ? $result['sid'] : '';
1534
					if (isset($result['sid'])) {
1535
						$subscriptionid = $result['sid'];
1536
					} else {
1537
						log_error("Azure subscription id not found in resource id");
1538
						return false;
1539
					}
1540
						// find tenant id from subscription id
1541
					curl_setopt($ch, CURLOPT_URL, "https://management.azure.com/subscriptions/" . $subscriptionid . "?api-version=2016-09-01");
1542
					curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1543
					curl_setopt($ch, CURLOPT_HEADER, 1);
1544
					curl_setopt($ch, CURLOPT_NOBODY, 1);
1545
					curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1546
					$output = curl_exec($ch);
1547
					$pattern = '/Bearer authorization_uri="https:\\/\\/login.windows.net\\/(?<tid>[^"]*)/i';
1548
					preg_match($pattern, $output, $result);
1549
					if (isset($result['tid'])) {
1550
						$tenantid = $result['tid'];
1551
					} else {
1552
						log_error("Tenant ID not found");
1553
						return false;
1554
					}
1555
						// get an bearer token
1556
					curl_setopt($ch, CURLOPT_URL, "https://login.microsoftonline.com/" . $tenantid . "/oauth2/token");
1557
					curl_setopt($ch, CURLOPT_POST, 1);
1558
					$body = "resource=" . urlencode("https://management.core.windows.net/") . "&grant_type=client_credentials&client_id=" . $app_id . "&client_secret=" . urlencode($client_secret);
1559
					curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
1560
					curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1561
					curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1562
					$server_output = curl_exec($ch);
1563
					$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
1564
					preg_match("/\"access_token\":\"(?<tok>[^\"]*)\"/", $server_output, $result);
1565
					if (isset($result['tok'])) {
1566
						$bearertoken = $result['tok'];
1567
					} else {
1568
						log_error("no valid bearer token");
1569
						return false;
1570
					}
1571
						// Update the DNS record
1572
					if ($this->_useIPv6) {
1573
						$url = "https://management.azure.com" . $resourceid . "/AAAA/" . $hostname . "?api-version=2017-09-01";
1574
						$body = '{"properties":{"TTL":"' . $newttl . '", "AAAARecords":[{"ipv6Address":"' . $newip . '"}]}}';
1575
					} else {
1576
						$url = "https://management.azure.com" . $resourceid . "/A/" . $hostname . "?api-version=2017-09-01";
1577
						$body = '{"properties":{"TTL":"' . $newttl . '", "ARecords":[{"ipv4Address":"' . $newip . '"}]}}';
1578
					}
1579
					$request_headers = array();
1580
					$request_headers[] = 'Accept: application/json';
1581
					$request_headers[] = 'Authorization: Bearer ' . $bearertoken;
1582
					$request_headers[] = 'Content-Type: application/json';
1583
					curl_setopt($ch, CURLOPT_URL, $url);
1584
					curl_setopt($ch, CURLOPT_USERAGENT, $this->_UserAgent);
1585
					curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers);
1586
					curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
1587
					curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
1588
					curl_setopt($ch, CURLOPT_HEADER, 1);
1589
					curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
1590
					break;
1591
				case 'linode':
1592
				case 'linode-v6':
1593
					$linode_api = "https://api.linode.com/v4";
1594
					$record_type = $this->_useIPv6 ? "AAAA" : "A";
1595
					$record_name = $this->_dnsHost == "@" ? "" : $this->_dnsHost;
1596
					$err_prefix = gettext("Dynamic DNS") . " {$this->_dnsService} ({$this->_FQDN}): (" . gettext("Error") . ")";
1597
					curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1598
					curl_setopt($ch, CURLOPT_HTTPHEADER, array(
1599
						'Accept: application/json',
1600
						'Content-Type: application/json',
1601
						'Authorization: Bearer ' . $this->_dnsPass
1602
					));
1603

    
1604
					// get domain id
1605
					curl_setopt($ch, CURLOPT_URL, "{$linode_api}/domains");
1606
					curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1607
					$domains_output = curl_exec($ch);
1608
					$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
1609
					if ( $http_code == 401 && preg_match('/invalid oauth token/i', $domains_output) ) {
1610
						log_error("$err_prefix " . gettext("querying domains") . ": " . gettext("User Authentication Failed"));
1611
						return false;
1612
					} else if ( $http_code == 401 ) {
1613
						log_error("$err_prefix " . gettext("querying domains") . ": " . gettext("User Authorization Failed"));
1614
						return false;
1615
					} else if ( $http_code != 200 ) {
1616
						log_error("$err_prefix " . gettext("querying domains") . ": " .
1617
							( isset($domains_result["errors"][0]["reason"]) ? $domains_result["errors"][0]["reason"] : "HTTP {$http_code}" ) );
1618
						return false;
1619
					}
1620

    
1621
					$domains_result = json_decode($domains_output, TRUE);
1622
					foreach($domains_result["data"] as $domains_entry) {
1623
						if ($domains_entry["domain"] == $this->_dnsDomain) {
1624
							$domain_id = $domains_entry["id"];
1625
						}
1626
					}
1627
					if ( ! $domain_id ) {
1628
						log_error("{$err_prefix} " . gettext("no domain ID for domain") . ": '{$this->_dnsDomain}'");
1629
						return false;
1630
					}
1631
					if ($this->_dnsVerboseLog) {
1632
						log_error("_update(): " . sprintf(gettext("found domain id: %s"), $domain_id));
1633
					}
1634

    
1635
					// get existing record if present
1636
					curl_setopt($ch, CURLOPT_URL, "{$linode_api}/domains/{$domain_id}/records");
1637
					curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1638
					$records_output = curl_exec($ch);
1639
					$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
1640
					if ( $http_code != 200 )
1641
					{
1642
						log_error("$err_prefix " . gettext("querying domains") . ": " .
1643
							( isset($domains_result["errors"][0]["reason"]) ? $domains_result["errors"][0]["reason"] : "HTTP {$http_code}" ) );
1644
						return false;
1645
					}
1646

    
1647
					$records_result = json_decode($records_output, TRUE);
1648
					foreach($records_result["data"] as $records_entry) {
1649
						if ( $records_entry["type"] == $record_type && $records_entry["name"] == $record_name ) {
1650
							// not adding support for pagination at this time, hope you have < 100 records!
1651
							$record = $records_entry;
1652
						}
1653
					}
1654
					if ($this->_dnsVerboseLog) {
1655
						log_error("_update(): " . ( $record ? sprintf(gettext("found existing record id: %s"), $record["id"]) : gettext("no matching record found") ));
1656
					}
1657

    
1658
					if (is_array($record)) {
1659
						// update existing record
1660
						$record["target"] = $this->_dnsIP;
1661
						$record["ttl_sec"] = (int) $this->_dnsTTL; // linode may round this up, 0 = zone default
1662
						curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($record));
1663
						curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
1664
						curl_setopt($ch, CURLOPT_URL, "{$linode_api}/domains/{$domain_id}/records/" . $record["id"]);
1665
					} else {
1666
						// create a new record
1667
						$record = array(
1668
							"type"    => $record_type,
1669
							"name"    => $record_name,
1670
							"target"  => $this->_dnsIP,
1671
							"ttl_sec" => (int) $this->_dnsTTL, // linode may round this up, 0 = zone default
1672
						);
1673
						curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($record));
1674
						curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
1675
						curl_setopt($ch, CURLOPT_URL, "{$linode_api}/domains/{$domain_id}/records");
1676
					}
1677
					break;
1678
				case 'dynv6':
1679
					$needIP = TRUE;
1680
					curl_setopt($ch, CURLOPT_URL, 'https://dynv6.com/api/update?ipv4=' . $this->_dnsIP . '&hostname=' . $this->_dnsHost . '&token=' . $this->_dnsPass);
1681
					break;
1682
				case 'dynv6-v6':
1683
					$needIP = TRUE;
1684
					curl_setopt($ch, CURLOPT_URL, 'https://dynv6.com/api/update?ipv6=' . $this->_dnsIP . '&hostname=' . $this->_dnsHost . '&token=' . $this->_dnsPass);
1685
					break;
1686
				case 'gandi-livedns':
1687
				case 'gandi-livedns-v6':
1688
					// https://api.gandi.net/docs/livedns/#v5-livedns-domains-fqdn-records-rrset_name-rrset_type
1689
					// PUT overrides the existing record or creates if none exists
1690
					$gandi_api = 'https://api.gandi.net/v5/livedns/domains/';
1691
					$record_type = $this->_useIPv6 ? "AAAA" : "A";
1692
					$url = "{$gandi_api}{$this->_dnsDomain}/records/{$this->_dnsHost}/{$record_type}";
1693

    
1694
					$headers = array(
1695
						'Authorization: Apikey ' . $this->_dnsPass,
1696
						'Content-Type: application/json'
1697
					);
1698
					$body = array(
1699
						"rrset_ttl"  => $this->_dnsTTL,
1700
						"rrset_values" => array($this->_dnsIP),
1701
					);
1702

    
1703
					curl_setopt($ch, CURLOPT_URL, $url);
1704
					curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
1705
					curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
1706
					curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
1707
					break;
1708
				case 'desec':
1709
					curl_setopt($ch, CURLOPT_URL, 'https://update.dedyn.io/?hostname=' . $this->_dnsHost);
1710
					curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Token ' . $this->_dnsPass));
1711
					break;
1712
				case 'desec-v6':
1713
					curl_setopt($ch, CURLOPT_URL, 'https://update6.dedyn.io/?hostname=' . $this->_dnsHost);
1714
					curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Token ' . $this->_dnsPass));
1715
					break;
1716
				default:
1717
					break;
1718
			}
1719
			if ($this->_dnsService != 'ods') {
1720
				curl_setopt($ch, CURLOPT_HEADER, 1);
1721
				if ($this->_curlProxy == true) {
1722
					set_curlproxy($ch);
1723
				}
1724
				curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1725
				$response = curl_exec($ch);
1726
				$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
1727
				$header = substr($response, 0, $header_size);
1728
				$data = substr($response, $header_size);
1729
				if ($this->_dnsVerboseLog) {
1730
					foreach (explode(PHP_EOL, $header) as $hv) {
1731
						log_error(sprintf("Response Header: %s", rtrim($hv)));
1732
					}
1733
					log_error(sprintf("Response Data: %s", $data));
1734
				}
1735
				$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
1736
				$curl_error = @curl_error($ch);
1737
				$this->_checkStatus($http_code, $curl_error, $data, $header);
1738
			}
1739
			if ($this->_dnsVerboseLog) {
1740
				log_error(sprintf(gettext('Dynamic DNS %1$s (%2$s): _update() ending.'), $this->_dnsService, $this->_FQDN));
1741
			}
1742
		}
1743

    
1744
		/**
1745
		 * Private Function (added 23 Feb 17)
1746
		 *   Send Removal To Selected Service.
1747
		 *
1748
		 *   Some services do not perform an inplace upgrade.  If they do not then the solution
1749
		 *   is to remove the existing record and add a new record.
1750
		 *
1751
		 * @param unknown $existing_ip If required, an existing IP address for the record.
1752
		 */
1753
		function _remove($existing_ip = NULL) {
1754
			$remove_allowed = false;
1755
			if ($this->_dnsVerboseLog) {
1756
				log_error(sprintf(gettext('Dynamic DNS %1$s (%2$s): _remove() starting.'), $this->_dnsService, $this->_FQDN));
1757
			}
1758

    
1759
			if (strstr($this->_dnsRequestIf, "_vip")) {
1760
				$parentif = get_configured_vip_interface($this->_dnsRequestIf);
1761
				$realparentif = convert_friendly_interface_to_real_interface_name($parentif);
1762
			} else {
1763
				$realparentif = $this->_dnsRequestIf;
1764
			}
1765

    
1766
			$ch = curl_init();
1767

    
1768
			if ($this->_useIPv6 == false) {
1769
				curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
1770
			}
1771

    
1772
			curl_setopt($ch, CURLOPT_USERAGENT, $this->_UserAgent);
1773
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
1774
			curl_setopt($ch, CURLOPT_INTERFACE, 'if!' . $realparentif);
1775
			curl_setopt($ch, CURLOPT_TIMEOUT, 120); // Completely empirical
1776

    
1777
			switch ($this->_dnsService) {
1778
			case 'dreamhost':
1779
			case 'dreamhost-v6':
1780
				$server = 'https://api.dreamhost.com/';
1781
				$post_data['key'] = $this->_dnsPass;
1782
				$post_data['unique_id'] = uniqid($this->_dnsHost);
1783
				$post_data['cmd'] = 'dns-remove_record';
1784
				$post_data['format'] = 'json';
1785
				$post_data['value'] = $existing_ip;
1786
				$post_data['record'] = $this->_dnsHost;
1787
				$isv6 = ($this->_dnsService === 'dreamhost-v6');
1788
				$post_data['type'] = $isv6 ? 'AAAA' : 'A';
1789
				$port = "";
1790
				if ($this->_dnsServer) {
1791
					$server = $this->_dnsServer;
1792
				}
1793
				if ($this->_dnsPort) {
1794
					$port = ":" . $this->_dnsPort;
1795
				}
1796
				curl_setopt($ch, CURLOPT_URL, $server . $port);
1797
				curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
1798
				$remove_allowed = true;
1799
				break;
1800
			default:
1801
				break;
1802
			}
1803
			if ($remove_allowed) {
1804
				curl_setopt($ch, CURLOPT_HEADER, 1);
1805
				curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1806
				$response = curl_exec($ch);
1807
				$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
1808
				$header = substr($response, 0, $header_size);
1809
				$data = substr($response, $header_size);
1810
				$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
1811
				$curl_error = @curl_error($ch);
1812
				$this->_checkStatus($http_code, $curl_error, $data, $header);
1813
			}
1814
		}
1815

    
1816
		/**
1817
		 * Private Function (added 23 Feb 17)
1818
		 * Retrieves current DNS records from an external API source.
1819
		 *
1820
		 * Some services cannot perform new operations without the caller
1821
		 * providing existing record information.
1822
		 */
1823
		function _lookup_current() {
1824
			$lookup_allowed = false;
1825
			if ($this->_dnsVerboseLog) {
1826
				log_error(sprintf(gettext('Dynamic DNS %1$s (%2$s): _listCurrent() starting.'), $this->_dnsService, $this->_FQDN));
1827
			}
1828

    
1829
			if (strstr($this->_dnsRequestIf, "_vip")) {
1830
				$parentif = get_configured_vip_interface($this->_dnsRequestIf);
1831
				$realparentif = convert_friendly_interface_to_real_interface_name($parentif);
1832
			} else {
1833
				$realparentif = $this->_dnsRequestIf;
1834
			}
1835

    
1836
			$ch = curl_init();
1837

    
1838
			if ($this->_useIPv6 == false) {
1839
				curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
1840
			}
1841

    
1842
			curl_setopt($ch, CURLOPT_USERAGENT, $this->_UserAgent);
1843
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
1844
			curl_setopt($ch, CURLOPT_INTERFACE, 'if!' . $realparentif);
1845
			curl_setopt($ch, CURLOPT_TIMEOUT, 120); // Completely empirical
1846

    
1847
			switch ($this->_dnsService) {
1848
			case 'dreamhost':
1849
			case 'dreamhost-v6':
1850
				$server = 'https://api.dreamhost.com/';
1851
				$post_data['key'] = $this->_dnsPass;
1852
				$post_data['unique_id'] = uniqid($this->_dnsHost);
1853
				$post_data['cmd'] = 'dns-list_records';
1854
				$post_data['format'] = 'json';
1855
				$port = "";
1856
				if ($this->_dnsServer) {
1857
					$server = $this->_dnsServer;
1858
				}
1859
				if ($this->_dnsPort) {
1860
					$port = ":" . $this->_dnsPort;
1861
				}
1862
				curl_setopt($ch, CURLOPT_URL, $server . $port);
1863
				curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
1864
				$lookup_allowed = true;
1865
				break;
1866
			default:
1867
				break;
1868
			}
1869
			if ($lookup_allowed) {
1870
				curl_setopt($ch, CURLOPT_HEADER, 1);
1871
				curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
1872
				$response = curl_exec($ch);
1873
				$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
1874
				$header = substr($response, 0, $header_size);
1875
				$data = substr($response, $header_size);
1876
				$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
1877
				$curl_error = @curl_error($ch);
1878
				$this->_checkLookupStatus($curl_error, $data, $header);
1879
			}
1880
		}
1881

    
1882
		/*
1883
		 * Private Function (added 23 Feb 17)
1884
		 *   Retrieve Lookup Status from the provided data and/or header
1885
		 */
1886
		function _checkLookupStatus($curl_error, $data, $header) {
1887
			if ($this->_dnsVerboseLog) {
1888
				log_error(sprintf(gettext('Dynamic DNS %1$s (%2$s): _checkLookupStatus() starting.'), $this->_dnsService, $this->_FQDN));
1889
			}
1890
			$success_str = "(" . gettext("Success") . ") ";
1891
			$error_str = "(" . gettext("Error") . ") ";
1892
			$status_intro = "phpDynDNS ({$this->_dnsHost}): ";
1893

    
1894
			if ($this->_dnsService != 'ods' && !empty($curl_error)) {
1895
				$status = gettext("Curl error occurred:") . " {$curl_error}";
1896
				log_error($status);
1897
				$this->status = $status;
1898
				return;
1899
			}
1900
			switch ($this->_dnsService) {
1901
			case 'dreamhost':
1902
			case 'dreamhost-v6':
1903
				$result = json_decode($data,true);
1904
				if($result["result"] != "success") {
1905
					log_error($status_intro . gettext("PAYLOAD:") . " {$data}");
1906
					$this->_debug($data);
1907
					return;
1908
				} else {
1909
					foreach($result["data"] as $key => $row) {
1910
						if($row["record"] == $this->_dnsHost &&
1911
								(($row["type"] == "A" && !$this->_useIPv6)
1912
										|| ($row["type"] == "AAAA" && $this->_useIPv6)
1913
								)) {
1914
							if($row["editable"] == 0) {
1915
								log_error($status_intro . "host " . $this->_dnsHost . " is not editable.");
1916
								continue;
1917
							}
1918
							$this->_existingRecords[]=array("record"=>$row["type"], "type"=>$row["type"], "existing_val"=>$row["value"]);
1919
						}
1920
					}
1921
				}
1922
				if (!is_array($this->_existingRecords)){
1923
					if ($this->_dnsVerboseLog) {
1924
						log_error(sprintf(gettext('Dynamic DNS %1$s (%2$s): _checkLookupStatus() ending.  No matching records found.'), $this->_dnsService, $this->_FQDN));
1925
					}
1926
				}
1927
				break;
1928
			default:
1929
				break;
1930
			}
1931
		}
1932

    
1933
		/*
1934
		 * Private Function (added 12 July 2005) [beta]
1935
		 *   Retrieve Update Status
1936
		 */
1937
		function _checkStatus($http_code, $curl_error, $data, $header) {
1938
			if ($this->_dnsVerboseLog) {
1939
				log_error(sprintf(gettext('Dynamic DNS %1$s (%2$s): _checkStatus() starting.'), $this->_dnsService, $this->_FQDN));
1940
			}
1941
			$successful_update = false;
1942
			$success_str = "(" . gettext("Success") . ") ";
1943
			$error_str = "(" . gettext("Error") . ") ";
1944
			$status_intro = "phpDynDNS ({$this->_dnsHost}): ";
1945

    
1946
			if ($this->_dnsService != 'ods' && !empty($curl_error)) {
1947
				$status = gettext("Curl error occurred:") . " {$curl_error}";
1948
				log_error($status);
1949
				$this->status = $status;
1950
				return;
1951
			}
1952
			switch ($this->_dnsService) {
1953
				// The special custom provider
1954
				case 'custom':
1955
				case 'custom-v6':
1956
					$successful_update = false;
1957
					if ($this->_dnsResultMatch == "") {
1958
						$successful_update = true;
1959
					} else {
1960
						$this->_dnsResultMatch = str_replace("%IP%", $this->_dnsIP, $this->_dnsResultMatch);
1961
						$matches = preg_split("/(?<!\\\\)\\|/", $this->_dnsResultMatch);
1962
						foreach ($matches as $match) {
1963
							$match= str_replace("\\|", "|", $match);
1964
							if (strcmp($match, trim($data, "\t\n\r")) == 0) {
1965
								$successful_update = true;
1966
							}
1967
						}
1968
						unset ($matches);
1969
					}
1970
					if ($successful_update == true) {
1971
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
1972
					} else {
1973
						$status = $status_intro . $error_str . gettext("Result did not match.") . " [" . $data . "]";
1974
					}
1975
					break;
1976
				// Providers in an alphabetical order. Add code for new providers below in a correct position.
1977
				case 'dyfi':
1978
					// see specification at https://www.dy.fi/page/specification
1979
					if (preg_match('/badauth/i', $data)) {
1980
						$status = $status_intro . $error_str . gettext("User Authorization Failed");
1981
					} else if (preg_match('/nohost/i', $data)) {
1982
						$status = $status_intro . $error_str . gettext("No such host");
1983
					} else if (preg_match('/notfqdn/i', $data)) {
1984
						$status = $status_intro . $error_str . gettext("Not A FQDN!");
1985
					} else if (preg_match('/badip/i', $data)) {
1986
						$status = $status_intro . $error_str . gettext("Invalid IP. IP Address submitted is improperly formatted or is a private IP address or is on a blacklist.");
1987
					} else if (preg_match('/nochg/i', $data)) {
1988
						$status = $status_intro . $success_str . gettext("No Change In IP Address");
1989
						$successful_update = true;
1990
					} else if (preg_match('/good/i', $data)) {
1991
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!") . " (" . $this->_dnsIP . ")";
1992
						$successful_update = true;
1993
					} else if (preg_match('/dnserr/i', $data)) {
1994
						$status = $status_intro . $error_str . gettext("Server side error.");
1995
					} else if (preg_match('/abuse/i', $data)) {
1996
						$status = $status_intro . $error_str . gettext("Updating too frequently, considered abuse.");
1997
					} else {
1998
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
1999
						log_error($status_intro . gettext("PAYLOAD:") . " {$data}");
2000
						$this->_debug($data);
2001
					}
2002
					break;
2003
				case 'glesys':
2004
					$status_intro = "GleSYS ({$this->_dnsHost}): ";
2005
					if (preg_match("/OK/i", $data)) {
2006
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!") . " (" . $this->_dnsIP . ")";
2007
						$successful_update = true;
2008
					} else {
2009
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2010
						log_error($status_intro . gettext("PAYLOAD:") . " {$data}");
2011
						$this->_debug($data);
2012
					}
2013
					break;
2014
				//
2015
				// Not yet ordered providers.
2016
				// TODO: When editing a provider, move it above in a correct position.
2017
				//
2018
				case 'dnsomatic':
2019
					$status_intro = "DNS-O-Matic ({$this->_dnsHost}): ";
2020
					if (preg_match('/badauth/i', $data)) {
2021
						$status = $status_intro . gettext("The DNS-O-Matic username or password specified are incorrect. No updates will be distributed to services until this is resolved.");
2022
					} else if (preg_match('/notfqdn /i', $data)) {
2023
						$status = $status_intro . gettext("The hostname specified is not a fully-qualified domain name. If no hostnames included, notfqdn will be returned once.");
2024
					} else if (preg_match('/nohost/i', $data)) {
2025
						$status = $status_intro . gettext("The hostname passed could not be matched to any services configured. The service field will be blank in the return code.");
2026
					} else if (preg_match('/numhost/i', $data)) {
2027
						$status = $status_intro . gettext("Up to 20 hosts my be updated. numhost is returned if attempting to update more than 20 or update a round-robin.");
2028
					} else if (preg_match('/abuse/i', $data)) {
2029
						$status = $status_intro . gettext("The hostname is blocked for update abuse.");
2030
					} else if (preg_match('/good/i', $data)) {
2031
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!") . " (" . $this->_dnsIP . ")";
2032
						$successful_update = true;
2033
					} else if (preg_match('/dnserr/i', $data)) {
2034
						$status = $status_intro . gettext("DNS error encountered. Stop updating for 30 minutes.");
2035
					} else {
2036
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2037
						log_error($status_intro . gettext("PAYLOAD:") . " {$data}");
2038
						$this->_debug($data);
2039
					}
2040
					break;
2041
				case 'domeneshop':
2042
				case 'domeneshop-v6':
2043
					/* Responds with HTTP 204 on success.
2044
					 * see https://api.domeneshop.no/docs/index.html#tag/ddns/paths/~1dyndns~1update/get */
2045

    
2046
					if ($http_code == "204") {
2047
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2048
						$successful_update = true;
2049
					} else {
2050
						$status = $status_intro . "(" . gettext("Unknown Response") . ": " . $http_code . ")";
2051
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2052
						$this->_debug($data);
2053
					}
2054
					break;
2055
				case 'onecom':
2056
				case 'onecom-v6':
2057

    
2058
					if (($http_code == "200") || ($http_code == "204")) {
2059
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2060
						$successful_update = true;
2061
					} else {
2062
						$status = $status_intro . "(" . gettext("Unknown Response") . ": " . $http_code . ")";
2063
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2064
						$this->_debug($data);
2065
					}
2066
					break;
2067
				case 'citynetwork':
2068
					if (preg_match('/notfqdn/i', $data)) {
2069
						$status = $status_intro . $error_str . gettext("Not A FQDN!");
2070
					} else if (preg_match('/nohost/i', $data)) {
2071
						$status = $status_intro . $error_str . gettext("No such host");
2072
					} else if (preg_match('/nochg/i', $data)) {
2073
						$status = $status_intro . $success_str . gettext("No Change In IP Address");
2074
						$successful_update = true;
2075
					} else if (preg_match('/good/i', $data)) {
2076
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!") . " (" . $this->_dnsIP . ")";
2077
						$successful_update = true;
2078
					} else if (preg_match('/badauth/i', $data)) {
2079
						$status = $status_intro . $error_str . gettext("User Authorization Failed");
2080
					} else {
2081
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2082
						log_error($status_intro . gettext("PAYLOAD:") . " {$data}");
2083
						$this->_debug($data);
2084
					}
2085
					break;
2086
				case 'ovh-dynhost':
2087
				case 'dyndns':
2088
					if (preg_match('/notfqdn/i', $data)) {
2089
						$status = $status_intro . $error_str . gettext("Not A FQDN!");
2090
					} else if (preg_match('/nochg/i', $data)) {
2091
						$status = $status_intro . $success_str . gettext("No Change In IP Address");
2092
						$successful_update = true;
2093
					} else if (preg_match('/good/i', $data)) {
2094
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!") . " (" . $this->_dnsIP . ")";
2095
						$successful_update = true;
2096
					} else if (preg_match('/noauth/i', $data)) {
2097
						$status = $status_intro . $error_str . gettext("User Authorization Failed");
2098
					} else {
2099
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2100
						log_error($status_intro . gettext("PAYLOAD:") . " {$data}");
2101
						$this->_debug($data);
2102
					}
2103
					break;
2104
				case 'dyndns-static':
2105
					if (preg_match('/notfqdn/i', $data)) {
2106
						$status = $status_intro . $error_str . gettext("Not A FQDN!");
2107
					} else if (preg_match('/nochg/i', $data)) {
2108
						$status = $status_intro . $success_str . gettext("No Change In IP Address");
2109
						$successful_update = true;
2110
					} else if (preg_match('/good/i', $data)) {
2111
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!");
2112
						$successful_update = true;
2113
					} else if (preg_match('/noauth/i', $data)) {
2114
						$status = $status_intro . $error_str . gettext("User Authorization Failed");
2115
					} else {
2116
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2117
						log_error($status_intro . gettext("PAYLOAD:") . " {$data}");
2118
						$this->_debug($data);
2119
					}
2120
					break;
2121
				case 'dyndns-custom':
2122
					if (preg_match('/notfqdn/i', $data)) {
2123
						$status = $status_intro . $error_str . gettext("Not A FQDN!");
2124
					} else if (preg_match('/nochg/i', $data)) {
2125
						$status = $status_intro . $success_str . gettext("No Change In IP Address");
2126
						$successful_update = true;
2127
					} else if (preg_match('/good/i', $data)) {
2128
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!");
2129
						$successful_update = true;
2130
					} else if (preg_match('/noauth/i', $data)) {
2131
						$status = $status_intro . $error_str . gettext("User Authorization Failed");
2132
					} else {
2133
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2134
						log_error($status_intro . gettext("PAYLOAD:") . " {$data}");
2135
						$this->_debug($data);
2136
					}
2137
					break;
2138
				case 'dhs':
2139
					break;
2140
				case 'noip':
2141
				case 'noip-free':
2142
					if (preg_match('/good/i', $data)) {
2143
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!");
2144
						$successful_update = true;
2145
					} else if (preg_match('/nochg/i', $data)) {
2146
						$status = $status_intro . $success_str . gettext("No Change In IP Address");
2147
						$successful_update = true;
2148
					} else if (preg_match('/nohost/i', $data)) {
2149
						$status = $status_intro . $error_str . gettext("Hostname supplied does not exist under specified account");
2150
					} else if (preg_match('/badauth/i', $data)) {
2151
						$status = $status_intro . $error_str . gettext("Invalid username password combination");
2152
					} else if (preg_match('/badagent/i', $data)) {
2153
						$status = $status_intro . $error_str . gettext("Client disabled");
2154
					} else if (preg_match('/\!donator/i', $data)) {
2155
						$status = $status_intro . $error_str . gettext("Feature is not available");
2156
					} else if (preg_match('/abuse/i', $data)) {
2157
						$status = $status_intro . $error_str . gettext("Username is blocked due to abuse");
2158
					} else if (preg_match('/911/i', $data)) {
2159
						$status = $status_intro . $error_str . gettext("Fatal error");
2160
					} else {
2161
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2162
						log_error($status_intro . gettext("PAYLOAD:") . " {$data}");
2163
						$this->_debug($data);
2164
					}
2165
					break;
2166
				case 'noip-v6':
2167
				case 'noip-free-v6':
2168
					list($ip, $code) = explode(":", $data);
2169
					switch ($code) {
2170
						case 0:
2171
							$status = $status_intro . $success_str . gettext("IP address is current, no update performed.");
2172
							$successful_update = true;
2173
							break;
2174
						case 1:
2175
							$status = $status_intro . $success_str . gettext("DNS hostname update successful.");
2176
							$successful_update = true;
2177
							break;
2178
						case 2:
2179
							$status = $status_intro . $error_str . gettext("Hostname supplied does not exist.");
2180
							break;
2181
						case 3:
2182
							$status = $status_intro . $error_str . gettext("Invalid Username.");
2183
							break;
2184
						case 4:
2185
							$status = $status_intro . $error_str . gettext("Invalid Password.");
2186
							break;
2187
						case 5:
2188
							$status = $status_intro . $error_str . gettext("Too many updates sent.");
2189
							break;
2190
						case 6:
2191
							$status = $status_intro . $error_str . gettext("Account disabled due to violation of No-IP terms of service.");
2192
							break;
2193
						case 7:
2194
							$status = $status_intro . $error_str . gettext("Invalid IP. IP Address submitted is improperly formatted or is a private IP address or is on a blacklist.");
2195
							break;
2196
						case 8:
2197
							$status = $status_intro . $error_str . gettext("Disabled / Locked Hostname.");
2198
							break;
2199
						case 9:
2200
							$status = $status_intro . $error_str . gettext("Host updated is configured as a web redirect and no update was performed.");
2201
							break;
2202
						case 10:
2203
							$status = $status_intro . $error_str . gettext("Group supplied does not exist.");
2204
							break;
2205
						case 11:
2206
							$status = $status_intro . $success_str . gettext("DNS group update is successful.");
2207
							$successful_update = true;
2208
							break;
2209
						case 12:
2210
							$status = $status_intro . $success_str . gettext("DNS group is current, no update performed.");
2211
							$successful_update = true;
2212
							break;
2213
						case 13:
2214
							$status = $status_intro . $error_str . gettext("Update client support not available for supplied hostname or group.");
2215
							break;
2216
						case 14:
2217
							$status = $status_intro . $error_str . gettext("Hostname supplied does not have offline settings configured.");
2218
							break;
2219
						case 99:
2220
							$status = $status_intro . $error_str . gettext("Client disabled. Client should exit and not perform any more updates without user intervention.");
2221
							break;
2222
						case 100:
2223
							$status = $status_intro . $error_str . gettext("Client disabled. Client should exit and not perform any more updates without user intervention.");
2224
							break;
2225
						default:
2226
							$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2227
							$this->_debug(gettext("Unknown Response:") . " " . $data);
2228
							break;
2229
					}
2230
					break;
2231
				case 'easydns':
2232
					if (preg_match('/NOACCESS/i', $data)) {
2233
						$status = $status_intro . $error_str . gettext("Authentication Failed: Username and/or Password was Incorrect.");
2234
					} else if (preg_match('/NOSERVICE/i', $data)) {
2235
						$status = $status_intro . $error_str . gettext("No Service: Dynamic DNS Service has been disabled for this domain.");
2236
					} else if (preg_match('/ILLEGAL INPUT/i', $data)) {
2237
						$status = $status_intro . $error_str . gettext("Illegal Input: Self-Explanatory");
2238
					} else if (preg_match('/TOOSOON/i', $data)) {
2239
						$status = $status_intro . $error_str . gettext("Too Soon: Not Enough Time Has Elapsed Since Last Update");
2240
					} else if (preg_match('/NOERROR/i', $data)) {
2241
						$status = $status_intro . $success_str . gettext("IP Updated Successfully!");
2242
						$successful_update = true;
2243
					} else {
2244
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2245
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2246
						$this->_debug($data);
2247
					}
2248
					break;
2249
				case 'easydns-v6':
2250
					if (preg_match('/NOACCESS/i', $data)) {
2251
						$status = $status_intro . $error_str . gettext("Authentication Failed: Username and/or Password was Incorrect.");
2252
					} else if (preg_match('/NOSERVICE/i', $data)) {
2253
						$status = $status_intro . $error_str . gettext("No Service: Dynamic DNS Service has been disabled for this domain.");
2254
					} else if (preg_match('/ILLEGAL INPUT/i', $data)) {
2255
						$status = $status_intro . $error_str . gettext("Illegal Input: Self-Explanatory");
2256
					} else if (preg_match('/TOOSOON/i', $data)) {
2257
						$status = $status_intro . $error_str . gettext("Too Soon: Not Enough Time Has Elapsed Since Last Update");
2258
					} else if (preg_match('/NOERROR/i', $data)) {
2259
						$status = $status_intro . $success_str . gettext("IP Updated Successfully!");
2260
						$successful_update = true;
2261
					} else {
2262
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2263
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2264
						$this->_debug($data);
2265
					}
2266
					break;
2267
				case 'hn':
2268
					/* FIXME: add checks */
2269
					break;
2270
				case 'zoneedit':
2271
					if (preg_match('/799/i', $data)) {
2272
						$status = $status_intro . "(" . gettext("Error 799") . ") " . gettext("Update Failed!");
2273
					} else if (preg_match('/700/i', $data)) {
2274
						$status = $status_intro . "(" . gettext("Error 700") . ") " . gettext("Update Failed!");
2275
					} else if (preg_match('/200/i', $data)) {
2276
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2277
						$successful_update = true;
2278
					} else if (preg_match('/201/i', $data)) {
2279
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2280
						$successful_update = true;
2281
					} else {
2282
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2283
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2284
						$this->_debug($data);
2285
					}
2286
					break;
2287
				case 'dyns':
2288
					if (preg_match("/400/i", $data)) {
2289
						$status = $status_intro . $error_str . gettext("Bad Request - The URL was malformed. Required parameters were not provided.");
2290
					} else if (preg_match('/402/i', $data)) {
2291
						$status = $status_intro . $error_str . gettext("Update Too Soon - Attempted to update too quickly since last change.");
2292
					} else if (preg_match('/403/i', $data)) {
2293
						$status = $status_intro . $error_str . gettext("Database Error - There was a server-sided database error.");
2294
					} else if (preg_match('/405/i', $data)) {
2295
						$status = $status_intro . $error_str . sprintf(gettext('Hostname Error - The hostname (%1$s) doesn\'t belong to user (%2$s).'), $this->_dnsHost, $this->_dnsUser);
2296
					} else if (preg_match('/200/i', $data)) {
2297
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2298
						$successful_update = true;
2299
					} else {
2300
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2301
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2302
						$this->_debug($data);
2303
					}
2304
					break;
2305
				case 'ods':
2306
					if (preg_match("/299/i", $data)) {
2307
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2308
						$successful_update = true;
2309
					} else {
2310
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2311
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2312
						$this->_debug($data);
2313
					}
2314
					break;
2315
				case 'freedns':
2316
				case 'freedns-v6':
2317
					if (preg_match("/has not changed./i", $data)) {
2318
						$status = $status_intro . $success_str . gettext("No Change In IP Address");
2319
						$successful_update = true;
2320
					} else if (preg_match("/Updated/i", $data)) {
2321
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!");
2322
						$successful_update = true;
2323
					} else {
2324
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2325
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2326
						$this->_debug($data);
2327
					}
2328
					break;
2329
				case 'freedns2':
2330
				case 'freedns2-v6':
2331
					if (preg_match("/No IP change detected/i", $data)) {
2332
						$status = $status_intro . $success_str . gettext("No Change In IP Address");
2333
						$successful_update = true;
2334
					} else if (preg_match("/Updated/i", $data)) {
2335
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!");
2336
						$successful_update = true;
2337
					} else {
2338
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2339
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2340
						$this->_debug($data);
2341
					}
2342
					break;
2343
				case 'dnsexit':
2344
					if (preg_match("/IP not changed/i", $data)) {
2345
						$status = $status_intro . $success_str . gettext("No Change In IP Address");
2346
						$successful_update = true;
2347
					} else if (preg_match("/Success/i", $data)) {
2348
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!");
2349
						$successful_update = true;
2350
					} else {
2351
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2352
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2353
						$this->_debug($data);
2354
					}
2355
					break;
2356
				case 'loopia':
2357
					if (preg_match("/nochg/i", $data)) {
2358
						$status = $status_intro . $success_str . gettext("No Change In IP Address");
2359
						$successful_update = true;
2360
					} else if (preg_match("/good/i", $data)) {
2361
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!");
2362
						$successful_update = true;
2363
					} else if (preg_match('/badauth/i', $data)) {
2364
						$status = $status_intro . $error_str . gettext("User Authorization Failed");
2365
					} else {
2366
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2367
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2368
						$this->_debug($data);
2369
					}
2370
					break;
2371
				case 'opendns':
2372
					if (preg_match('/badauth/i', $data)) {
2373
						$status = $status_intro . $error_str . gettext("Not a valid username or password!");
2374
					} else if (preg_match('/nohost/i', $data)) {
2375
						$status = $status_intro . $error_str . gettext("Hostname specified does not exist.");
2376
						$successful_update = true;
2377
					} else if (preg_match('/good/i', $data)) {
2378
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!") . " (" . $this->_dnsIP . ")";
2379
						$successful_update = true;
2380
					} else if (preg_match('/yours/i', $data)) {
2381
						$status = $status_intro . $error_str . gettext("Hostname specified exists, but not under the username specified.");
2382
					} else if (preg_match('/abuse/i', $data)) {
2383
						$status = $status_intro . $error_str . gettext("Updating too frequently, considered abuse.");
2384
					} else {
2385
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2386
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2387
						$this->_debug($data);
2388
					}
2389
					break;
2390
				case 'staticcling':
2391
					if (preg_match("/invalid ip/i", $data)) {
2392
						$status = $status_intro . $error_str . gettext("Bad Request - The IP provided was invalid.");
2393
					} else if (preg_match('/required info missing/i', $data)) {
2394
						$status = $status_intro . $error_str . gettext("Bad Request - Required parameters were not provided.");
2395
					} else if (preg_match('/invalid characters/i', $data)) {
2396
						$status = $status_intro . $error_str . gettext("Bad Request - Illegal characters in either the username or the password.");
2397
					} else if (preg_match('/bad password/i', $data)) {
2398
						$status = $status_intro . $error_str . gettext("Invalid password.");
2399
					} else if (preg_match('/account locked/i', $data)) {
2400
						$status = $status_intro . $error_str . gettext("This account has been administratively locked.");
2401
					} else if (preg_match('/update too frequent/i', $data)) {
2402
						$status = $status_intro . $error_str . gettext("Updating too frequently.");
2403
					} else if (preg_match('/DB error/i', $data)) {
2404
						$status = $status_intro . $error_str . gettext("Server side error.");
2405
					} else if (preg_match('/success/i', $data)) {
2406
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2407
						$successful_update = true;
2408
					} else {
2409
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2410
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2411
						$this->_debug($data);
2412
					}
2413
					break;
2414
				case 'mythicbeasts':
2415
				case 'mythicbeasts-v6':
2416
					/* https://www.mythic-beasts.com/support/api/dnsv2/tutorial */
2417
					$result = json_decode($data, true);
2418
					if ($result['message'] == '1 records added') {
2419
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2420
						$successful_update = true;
2421
					} else {
2422
						log_error($status_intro . " ( " . gettext("Error message: ") . $result['error'] . " )");
2423
					}
2424
					break;
2425
				case 'name.com':
2426
				case 'name.com-v6':
2427
					if ($http_code == "200") {
2428
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2429
						$successful_update = true;
2430
					} else {
2431
						$status = $status_intro . "(" . gettext("Error, HTTP response code") . ": " . $http_code . ")";
2432
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2433
					}
2434
					break;
2435
				case 'namecheap':
2436
					$tmp = str_replace("^M", "", $data);
2437
					$ncresponse = @xml2array($tmp);
2438
					/* If XML parsing failed, it may be due to unsupported encoding on the response. */
2439
					if (empty($ncresponse)) {
2440
						mb_convert_encoding($tmp, 'UTF-8', 'UTF-16');
2441
						$tmp = str_ireplace('utf-16', 'utf-8', $tmp);
2442
						$ncresponse = @xml2array($tmp);
2443
					}
2444
					/* If it's still empty, try parsing without the XML definition */
2445
					if (empty($ncresponse)) {
2446
						$matches = [];
2447
						preg_match("/(<?xml.*?>)(.*)/", $tmp, $matches);
2448
						if (count($matches) == 3) {
2449
							$ncresponse = @xml2array($matches[2]);
2450
						}
2451
					}
2452
					if (preg_match("/internal server error/i", $data)) {
2453
						$status = $status_intro . $error_str . gettext("Server side error.");
2454
					} else if (preg_match("/request is badly formed/i", $data)) {
2455
						$status = $status_intro . $error_str . gettext("Badly Formed Request (check the settings).");
2456
					} else if ($ncresponse['interface-response']['ErrCount'] === "0") {
2457
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2458
						$successful_update = true;
2459
					} else if (is_numeric($ncresponse['interface-response']['ErrCount']) && ($ncresponse['interface-response']['ErrCount'] > 0)) {
2460
						$status = $status_intro . $error_str . implode(", ", $ncresponse["interface-response"]["errors"]);
2461
					} else {
2462
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2463
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2464
						$this->_debug($data);
2465
					}
2466
					break;
2467
				case 'nicru':
2468
				case 'nicru-v6':
2469
					$status_intro = "NIC.RU ({$this->_dnsHost}): ";
2470
					if (preg_match('/badauth/i', $data)) {
2471
						$status = $status_intro . gettext("The NIC.RU username or password specified are incorrect.");
2472
					} else if (preg_match('/notfqdn /i', $data)) {
2473
						$status = $status_intro . gettext("The hostname specified is not a fully-qualified domain name.");
2474
					} else if (preg_match('/nohost/i', $data)) {
2475
						$status = $status_intro . gettext("The hostname passed could not be matched to any records configured.");
2476
					} else if (preg_match('/good/i', $data)) {
2477
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!") . " (" . $this->_dnsIP . ")";
2478
						$successful_update = true;
2479
					} else if (preg_match('/dnserr/i', $data)) {
2480
						$status = $status_intro . gettext("DNS error encountered. Stop updating for 30 minutes.");
2481
					} else {
2482
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2483
						log_error($status_intro . gettext("PAYLOAD:") . " {$data}");
2484
						$this->_debug($data);
2485
					}
2486
					break;
2487
				case 'duiadns':
2488
				case 'duiadns-v6':
2489
					if (preg_match("/error/i", $data)) {
2490
						$status = $status_intro . $error_str . gettext("Server side error.");
2491
					} else if (preg_match('/nohost/i', $data)) {
2492
						$status = $status_intro . $error_str . gettext("Bad Request - A hostname was not provided.");
2493
					} else if (preg_match('/badauth/i', $data)) {
2494
						$status = $status_intro . $error_str . gettext("Invalid username or password.");
2495
					} else if (preg_match('/good/i', $data)) {
2496
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2497
						$successful_update = true;
2498
					} else if (preg_match('/nochg/i', $data)) {
2499
						$status = $status_intro . $success_str . gettext("No Change In IP Address.");
2500
						$successful_update = true;
2501
					} else {
2502
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2503
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2504
						$this->_debug($data);
2505
					}
2506
					break;
2507
				case 'he-net':
2508
				case 'he-net-v6':
2509
				case 'he-net-tunnelbroker':
2510
					if (preg_match("/badip/i", $data)) {
2511
						$status = $status_intro . $error_str . gettext("Bad Request - The IP provided was invalid.");
2512
					} else if (preg_match('/nohost/i', $data)) {
2513
						$status = $status_intro . $error_str . gettext("Bad Request - A hostname was not provided.");
2514
					} else if (preg_match('/badauth/i', $data)) {
2515
						$status = $status_intro . $error_str . gettext("Invalid username or password.");
2516
					} else if (preg_match('/good/i', $data)) {
2517
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2518
						$successful_update = true;
2519
					} else if (preg_match('/nochg/i', $data)) {
2520
						$status = $status_intro . $success_str . gettext("No Change In IP Address.");
2521
						$successful_update = true;
2522
					} else {
2523
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2524
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2525
						$this->_debug($data);
2526
					}
2527
					break;
2528
				case 'selfhost':
2529
					if (preg_match('/notfqdn/i', $data)) {
2530
						$status = $status_intro . $error_str . gettext("Not A FQDN!");
2531
					} else if (preg_match('/nochg/i', $data)) {
2532
						$status = $status_intro . $success_str . gettext("No Change In IP Address.");
2533
						$successful_update = true;
2534
					} else if (preg_match('/good/i', $data)) {
2535
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!") . " (" . $this->_dnsIP . ")";
2536
						$successful_update = true;
2537
					} else if (preg_match('/noauth/i', $data)) {
2538
						$status = $status_intro . $error_str . gettext("User Authorization Failed");
2539
					} else {
2540
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2541
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2542
						$this->_debug($data);
2543
					}
2544
					break;
2545
				case 'strato':
2546
					if (preg_match('/good\s' . $this->_dnsIP . '/i', $data)) {
2547
						$status = $status_intro . $success_str . gettext("IP address updated successfully") . " (" . $this->_dnsIP . ")";
2548
						$successful_update = true;
2549
					} else if (preg_match('/good/i', $data)) {
2550
						$status = $status_intro . $error_str . gettext("IP result did not match");
2551
					} else if (preg_match('/nochg/i', $data)) {
2552
						$status = $status_intro . $success_str . gettext("IP was not changed");
2553
						$successful_update = true;
2554
					} else if (preg_match("/badauth/i", $data)) {
2555
						$status = $status_intro . $error_str . gettext("invalid username or password");
2556
					} else if (preg_match('/nohost/i', $data)) {
2557
						$status = $status_intro . $error_str . gettext("invalid hostname");
2558
					} else if (preg_match('/abuse/i', $data)) {
2559
						$status = $status_intro . $error_str . gettext("too many requests - please wait");
2560
					} else {
2561
						$status = $status_intro . "(" . gettext("unknown response") . ")";
2562
						log_error($status_intro . gettext("PAYLOAD:") . " " . $header . $data);
2563
						$this->_debug($data);
2564
						$this->_debug($header);
2565
					}
2566
					break;
2567
				case 'route53':
2568
				case 'route53-v6':
2569
					if(preg_match('/ErrorResponse/', $data)){
2570
						$status = $status_intro . $error_str . gettext("Route53 API call failed");
2571
						log_error(sprintf("error message: %s", $data));
2572
						$status_update = false;
2573
					} else {
2574
						$status = $status_intro . $success_str . gettext("IP address changed successfully");
2575
						$successful_update = true;
2576
					}
2577
					break;
2578
				case 'cloudflare-v6':
2579
				case 'cloudflare':
2580
					$output = json_decode($data);
2581
					if ($output->result->content === $this->_dnsIP) {
2582
						$status = $status_intro . $success_str . sprintf(gettext('%1$s updated to %2$s'), $this->_dnsHost, $this->_dnsIP);
2583
						$successful_update = true;
2584
					} elseif ($output->errors[0]->code === 9103) {
2585
						$status = $status_intro . $error_str . gettext("Invalid Credentials! Don't forget to use API Key for password field with Cloudflare.");
2586
					} elseif (($output->success) && (!$output->result[0]->id)) {
2587
						$status = $status_intro . $error_str . gettext("Zone or Host ID was not found, check the hostname.");
2588
					} else {
2589
						$status = $status_intro . gettext("UNKNOWN ERROR") . " - " . $output->errors[0]->message;
2590
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2591
					}
2592
					break;
2593
				case 'yandex-v6':
2594
				case 'yandex':
2595
					$result = json_decode($data, true);
2596
					if ($result['success'] == 'ok') {
2597
						$successful_update = true;
2598
					} elseif ($result['error'] == 'bad_domain') {
2599
						log_error($status_intro . $error_str .  gettext("The domain name was not specified or does not conform to the RFC"));
2600
					} elseif ($result['error'] == 'unknown') {
2601
						log_error($status_intro . $error_str .  gettext("A temporary failure or API error occurred"));
2602
					} elseif ($result['error'] == 'prohibited') {
2603
						log_error($status_intro . $error_str .  gettext("A forbidden domain name"));
2604
					} elseif ($result['error'] == 'bad_token') {
2605
						log_error($status_intro . $error_str .  gettext("Invalid PDD token"));
2606
					} elseif ($result['error'] == 'no_auth') {
2607
						log_error($status_intro . $error_str .  gettext("The PddToken header was omitted"));
2608
					} elseif ($result['error'] == 'not_allowed') {
2609
						log_error($status_intro . $error_str .  gettext("This operation is not allowed for this user"));
2610
					} elseif ($result['error'] == 'blocked') {
2611
						log_error($status_intro . $error_str .  gettext("Blocked domain"));
2612
					} elseif ($result['error'] == 'occupied') {
2613
						log_error($status_intro . $error_str .  gettext("The domain name is in use by another user"));
2614
					} elseif ($result['error'] == 'domain_limit_reached') {
2615
						log_error($status_intro . $error_str .  gettext("Exceeded the acceptable number of connected domains (50)"));
2616
					} elseif ($result['error'] == 'no_reply') {
2617
						log_error($status_intro . $error_str .  gettext("Yandex.Mail for Domain cannot connect to the server source for the import"));
2618
					} else {
2619
						log_error($status_intro . $error_str .  gettext("PAYLOAD:") . " " . $data);
2620
					}
2621
					break;
2622
				case 'eurodns':
2623
					if (preg_match('/notfqdn/i', $data)) {
2624
						$status = $status_intro . $error_str . gettext("Not A FQDN!");
2625
					} else if (preg_match('/nochg/i', $data)) {
2626
						$status = $status_intro . $success_str . gettext("No Change In IP Address");
2627
						$successful_update = true;
2628
					} else if (preg_match('/good/i', $data)) {
2629
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!") . " (" . $this->_dnsIP . ")";
2630
						$successful_update = true;
2631
					} else if (preg_match('/badauth/i', $data)) {
2632
						$status = $status_intro . $error_str . gettext("User Authorization Failed");
2633
					} else {
2634
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2635
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2636
						$this->_debug($data);
2637
					}
2638
					break;
2639
				case 'gratisdns':
2640
					if (preg_match('/Forkerte værdier/i', $data)) {
2641
						$status = $status_intro . $error_str . gettext("Wrong values - Update could not be completed.");
2642
					} else if (preg_match('/Bruger login: Bruger eksistere ikke/i', $data)) {
2643
						$status = $status_intro . $error_str . gettext("Unknown username - User does not exist.");
2644
					} else if (preg_match('/Bruger login: 1Fejl i kodeord/i', $data)) {
2645
						$status = $status_intro . $error_str . gettext("Wrong password - Remember password is case sensitive.");
2646
					} else if (preg_match('/Domæne kan IKKE administreres af bruger/i', $data)) {
2647
						$status = $status_intro . $error_str . gettext("User unable to administer the selected domain.");
2648
					} else if (preg_match('/OK/i', $data)) {
2649
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2650
						$successful_update = true;
2651
					} else {
2652
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2653
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2654
						$this->_debug($data);
2655
					}
2656
					break;
2657
				case 'digitalocean':
2658
				case 'digitalocean-v6':
2659
					// Creating new records returns an HTTP 201, updating existing records get 200
2660
					// https://redmine.pfsense.org/issues/9171
2661
					if (preg_match("/HTTP\/2\s20[0,1]/i", $header)) {
2662
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2663
						$successful_update = true;
2664
					} else {
2665
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2666
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2667
						$this->_debug($data);
2668
					}
2669
					break;
2670
				case 'dnsimple':
2671
				case 'dnsimple-v6':
2672
					/* Responds with HTTP 200 on success.
2673
					   Responds with HTTP 4xx on error.
2674
					   Returns JSON data as body */
2675
;
2676
					if ($http_code == "200") {
2677
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2678
						$successful_update = true;
2679
					} else if (preg_match("/4\d\d/i", $http_code)) {
2680
						$arrbody = json_decode($data, true);
2681
						$message = $arrbody['message'] . ".";
2682
						if (isset($arrbody['errors']['content'])) {
2683
							foreach ($arrbody['errors']['content'] as $key => $content) {
2684
								$message .= " " . $content . ".";
2685
							}
2686
						}
2687
						$status = $status_intro . $error_str . $message;
2688
					} else {
2689
						$status = $status_intro . "(" . gettext("Unknown Response") . ": " . $http_code . ")";
2690
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2691
						$this->_debug($data);
2692
					}
2693
					break;
2694
				case 'godaddy':
2695
				case 'godaddy-v6':
2696
					/* Responds with HTTP 200 on success.
2697
					   Responds with HTTP 4xx or  on error.
2698
					   Returns JSON data as body */
2699
;
2700
					if (preg_match("/\s200\sOK/i", $header)) {
2701
						$status = $status_intro . $success_str . gettext("IP Address Updated Successfully!");
2702
						$successful_update = true;
2703
					} else if (preg_match("/\s4\d\d\s/i", $header)) {
2704
						$arrbody = json_decode($data, true);
2705
						$message = $arrbody['message'] . ".";
2706
						if (isset($arrbody['fields'])) {
2707
							foreach ($arrbody['fields'] as $error) {
2708
								$message .= " " . $error['path'] . ": " . $error['message'] . ".";
2709
							}
2710
						}
2711
						$status = $status_intro . $error_str . $message;
2712
					} else {
2713
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2714
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2715
						$this->_debug($data);
2716
					}
2717
					break;
2718
				case 'googledomains':
2719
					if (preg_match('/notfqdn/i', $data)) {
2720
						$status = $status_intro . $error_str . gettext("Not A FQDN");
2721
					} else if (preg_match('/nochg/i', $data)) {
2722
						$status = $status_intro . $success_str . gettext("No Change In IP Address");
2723
						$successful_update = true;
2724
					} else if (preg_match('/good/i', $data)) {
2725
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!") . " (" . $this->_dnsIP . ")";
2726
						$successful_update = true;
2727
					} else if (preg_match('/badauth/i', $data)) {
2728
						$status = $status_intro . $error_str . gettext("User Authorization Failed");
2729
					} else if (preg_match('/nohost/i', $data)) {
2730
						$status = $status_intro . $error_str . gettext("Hostname does not exist or DynDNS not enabled");
2731
					} else if (preg_match('/badagent/i', $data)) {
2732
						$status = $status_intro . $error_str . gettext("Bad request");
2733
					} else if (preg_match('/abuse/i', $data)) {
2734
						$status = $status_intro . $error_str . gettext("Dynamic DNS access has been blocked!");
2735
					} else if (preg_match('/911/i', $data)) {
2736
						$status = $status_intro . $error_str . gettext("Error on Google's end, retry in 5 minutes");
2737
					} else {
2738
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2739
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2740
						$this->_debug($data);
2741
					}
2742
					break;
2743
				case 'dnsmadeeasy':
2744
					switch ($data) {
2745
						case 'success':
2746
							$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!") . " (" . $this->_dnsIP . ")";
2747
							$successful_update = true;
2748
							break;
2749
						case 'error-auth':
2750
							$status = $status_intro . $error_str . gettext("Invalid username or password");
2751
							break;
2752
						case 'error-auth-suspend':
2753
							$status = $status_intro . $error_str . gettext("Account suspended");
2754
							break;
2755
						case 'error-auth-voided':
2756
							$status = $status_intro . $error_str . gettext("Account revoked");
2757
							break;
2758
						case 'error-record-invalid':
2759
							$status = $status_intro . $error_str . gettext("Record does not exist in the system. Unable to update record");
2760
							break;
2761
						case 'error-record-auth':
2762
							$status = $status_intro . $error_str . gettext("User does not have access to this record");
2763
							break;
2764
						case 'error-record-ip-same':
2765
							$status = $status_intro . $success_str . gettext("No Change In IP Address");
2766
							$successful_update = true;
2767
							break;
2768
						case 'error-system':
2769
							$status = $status_intro . $error_str . gettext("General system error recognized by the system");
2770
							break;
2771
						case 'error':
2772
							$status = $status_intro . $error_str . gettext("General system error unrecognized by the system");
2773
							break;
2774
						default:
2775
							$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2776
							log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2777
							$this->_debug($data);
2778
							break;
2779
					}
2780
					break;
2781
				case 'spdyn':
2782
				case 'spdyn-v6':
2783
					if (preg_match('/notfqdn/i', $data)) {
2784
						$status = $status_intro . $error_str . gettext("Not A FQDN!");
2785
					} else if (preg_match('/nohost/i', $data)) {
2786
						$status = $status_intro . $error_str . gettext("No such host");
2787
					} else if (preg_match('/nochg/i', $data)) {
2788
						$status = $status_intro . $success_str . gettext("No Change In IP Address");
2789
						$successful_update = true;
2790
					} else if (preg_match('/good/i', $data)) {
2791
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!") . " (" . $this->_dnsIP . ")";
2792
						$successful_update = true;
2793
					} else if (preg_match('/badauth/i', $data)) {
2794
						$status = $status_intro . $error_str . gettext("User Authorization Failed");
2795
					} else {
2796
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2797
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2798
						$this->_debug($data);
2799
					}
2800
					break;
2801
				case 'all-inkl':
2802
 					if (preg_match('/good\s' . $this->_dnsIP . '/i', $data)) {
2803
							$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!") . " (" . $this->_dnsIP . ")";
2804
 							$successful_update = true;
2805
 					} else if (preg_match('/good/i', $data)) {
2806
						$status = $status_intro . $error_str . gettext("Result did not match.");
2807
					} else if (preg_match("/\s401\sUnauthorized/i", $header)) {
2808
						$status = $status_intro . $error_str . gettext("Invalid username or password");
2809
					}
2810
					else {
2811
							$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2812
							log_error($status_intro . gettext("PAYLOAD:") . " " . $header . $data);
2813
 							$this->_debug($data);
2814
 							$this->_debug($header);
2815
 					}
2816
 					break;
2817
				case 'hover':
2818
					if (preg_match('/succeeded":true/i', $data)) {
2819
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!") . " (" . $this->_dnsIP . ")";
2820
						$successful_update = true;
2821
					} else {
2822
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2823
						log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2824
						$this->_debug($data);
2825
					}
2826
					break;
2827
				case 'cloudns':
2828
					$result = json_decode($data, true);
2829
					if ($result['status'] == 'Success') {
2830
						$successful_update = true;
2831
					} else {
2832
						log_error($result['status'] . "(" . $result['statusDescription'] . ")");
2833
					}
2834
					break;
2835
				case 'dreamhost':
2836
				case 'dreamhost-v6':
2837
					$result = json_decode($data,true);
2838
					if ($this->_dnsVerboseLog) {
2839
						log_error(sprintf(gettext('_checkStatus() results: %1$s'), $data));
2840
					}
2841
					switch ($result['data']) {
2842
					case 'success':
2843
					case 'record_added':
2844
					case 'record_removed':
2845
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!") . " (" . $this->_dnsIP . ")";
2846
						$successful_update = true;
2847
						break;
2848
					case 'no_record':
2849
					case 'no_such_record ':
2850
						$status = $status_intro . $error_str . gettext("No record exists.");
2851
						break;
2852
					case 'no_type':
2853
					case 'no_such_type ':
2854
						$status = $status_intro . $error_str . gettext("No type exists.");
2855
						break;
2856
					case 'no_value':
2857
					case 'no_such_value ':
2858
						$status = $status_intro . $error_str . gettext("No value exists.");
2859
						break;
2860
					case 'no_such_zone':
2861
						$status = $status_intro . $error_str . gettext("No such zone exists.");
2862
						break;
2863
					case 'invalid_record':
2864
						$status = $status_intro . $error_str . gettext("The specified record is invalid.");
2865
						break;
2866
					case 'invalid_type':
2867
						$status = $status_intro . $error_str . gettext("The specified type is invalid.");
2868
						break;
2869
					case 'invalid_value':
2870
						$status = $status_intro . $error_str . gettext("The specified value is invalid.");
2871
						break;
2872
					case 'not_editable ':
2873
						$status = $status_intro . $error_str . gettext("Record is not editable.");
2874
						break;
2875
					case 'record_already_exists_not_editable':
2876
						$status = $status_intro . $error_str . gettext("Record exists but is not editable.");
2877
						break;
2878
					case 'record_already_exists_remove_first':
2879
						$status = $status_intro . $error_str . gettext("Record exists and must be removed before adding.");
2880
						break;
2881
					case 'internal_error_updating_zone':
2882
						$status = $status_intro . $error_str . gettext("A remote server error occurred updating the zone.");
2883
						break;
2884
					case 'internal_error_could_not_load_zone':
2885
						$status = $status_intro . $error_str . gettext("A remote server error occurred loading the zone.");
2886
						break;
2887
					case 'internal_error_could_not_update_zone':
2888
						$status = $status_intro . $error_str . gettext("A remote server error occurred updating the zone.");
2889
						break;
2890
					case 'internal_error_could_not_add_record':
2891
						$status = $status_intro . $error_str . gettext("A remote server error occurred adding a new record.");
2892
						break;
2893
					case 'internal_error_could_not_destroy_record ':
2894
						$status = $status_intro . $error_str . gettext("A remote server error occurred removing an existing record.");
2895
						break;
2896
					default:
2897
						break;
2898
					}
2899
				case 'azure':
2900
				case 'azurev6':
2901
					if ($http_code == 401) {
2902
						$status = $status_intro . $error_str . gettext("User Authorization Failed");
2903
					} else if ($http_code == 201) {
2904
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!");
2905
						$successful_update = true;
2906
					} else if ($http_code == 200) {
2907
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!");
2908
						$successful_update = true;
2909
					} else {
2910
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2911
						log_error($status_intro . gettext("PAYLOAD:") . " " . $http_code);
2912
						$this->_debug($data);
2913
					}
2914
					break;
2915
				case 'linode':
2916
				case 'linode-v6':
2917
					$status_intro = gettext("Dynamic DNS") . " {$this->_dnsService} ({$this->_FQDN}): ";
2918
					$result = json_decode($data,true);
2919
					if ($this->_dnsVerboseLog) {
2920
						log_error(sprintf(gettext('_checkStatus() results: %1$s'), $data));
2921
					}
2922
					if ($http_code == 200 && isset($result["id"]) && ! isset($result["errors"])) {
2923
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!");
2924
						$successful_update = true;
2925
					} else if ( $http_code == 401 && preg_match('/not authorized to use/i', $data) ) {
2926
						$status = $status_intro . $error_str . gettext("User Authorization Failed");
2927
					} else {
2928
						$status = $status_intro . $error_str .
2929
							( isset($domains_result["errors"][0]["reason"]) ? $domains_result["errors"][0]["reason"] : "HTTP {$http_code}" );
2930
					}
2931
					break;
2932
				case 'gandi-livedns':
2933
				case 'gandi-livedns-v6':
2934
					if ($http_code == 401) {
2935
						$status = $status_intro . $error_str . gettext("User Authorization Failed");
2936
					} else if ($http_code == 200 || $http_code == 201) {
2937
						$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!");
2938
						$successful_update = true;
2939
					} else {
2940
						$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2941
						log_error($status_intro . gettext("PAYLOAD:") . " " . $http_code);
2942
						$this->_debug($data);
2943
					}
2944
					break;
2945
				case 'desec':
2946
				case 'desec-v6':
2947
					switch ($http_code) {
2948
						case '200':
2949
							$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!");
2950
							$successful_update = true;
2951
							break;
2952
						case '401':
2953
							$status = $status_intro . $error_str . gettext("User Authorization Failed");
2954
							break;
2955
						default:
2956
							$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2957
							log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2958
							$this->_debug($data);
2959
							break;
2960
					}
2961
					break;
2962
				case 'dynv6':
2963
					switch ($http_code) {
2964
						case '200':
2965
							$status = $status_intro . $success_str . gettext("IP Address Changed Successfully!");
2966
							$successful_update = true;
2967
							break;
2968
						case '401':
2969
							$status = $status_intro . $error_str . gettext("User Authorization Failed");
2970
							break;
2971
						default:
2972
							$status = $status_intro . "(" . gettext("Unknown Response") . ")";
2973
							log_error($status_intro . gettext("PAYLOAD:") . " " . $data);
2974
							$this->_debug($data);
2975
							break;
2976
					}
2977
					break;
2978
				default:
2979
					break;
2980
			}
2981

    
2982
			if ($successful_update == true) {
2983
				/* Write WAN IP to cache file */
2984
				$wan_ip = $this->_checkIP();
2985
				if ($this->_useIPv6 == false && $wan_ip > 0) {
2986
					$currentTime = time();
2987
					notify_all_remote(sprintf(gettext('DynDNS updated IP Address on %1$s (%2$s) to %3$s'), convert_real_interface_to_friendly_descr($this->_if), $this->_if, $wan_ip));
2988
					log_error(sprintf(gettext('phpDynDNS: updating cache file %1$s: %2$s'), $this->_cacheFile, $wan_ip));
2989
					@file_put_contents($this->_cacheFile, "{$wan_ip}|{$currentTime}");
2990
				} else {
2991
					@unlink($this->_cacheFile);
2992
				}
2993
				if ($this->_useIPv6 == true && $wan_ip > 0) {
2994
					$currentTime = time();
2995
					notify_all_remote(sprintf(gettext('DynDNS updated IPv6 Address on %1$s (%2$s) to %3$s'), convert_real_interface_to_friendly_descr($this->_if), $this->_if, $wan_ip));
2996
					log_error(sprintf(gettext('phpDynDNS: updating cache file %1$s: %2$s'), $this->_cacheFile_v6, $wan_ip));
2997
					@file_put_contents($this->_cacheFile_v6, "{$wan_ip}|{$currentTime}");
2998
				} else {
2999
					@unlink($this->_cacheFile_v6);
3000
				}
3001
			}
3002
			$this->status = $status;
3003
			log_error($status);
3004
			if ($this->_dnsVerboseLog) {
3005
				log_error(sprintf(gettext('Dynamic DNS %1$s (%2$s): _checkStatus() ending.'), $this->_dnsService, $this->_FQDN));
3006
			}
3007
		}
3008

    
3009
		/*
3010
		 * Private Function (added 12 July 05) [beta]
3011
		 *   Return Error, Set Last Error, and Die.
3012
		 */
3013
		function _error($errorNumber = '1') {
3014
			$err_str = 'phpDynDNS: (' . gettext('ERROR!') . ') ';
3015
			$err_str_r53 = 'Route 53: (' . gettext('Error') . ') ';
3016
			switch ($errorNumber) {
3017
				case 0:
3018
					break;
3019
				case 2:
3020
					$error = $err_str . gettext('No Dynamic DNS Service provider was selected.');
3021
					break;
3022
				case 3:
3023
					$error = $err_str . gettext('No Username Provided.');
3024
					break;
3025
				case 4:
3026
					$error = $err_str . gettext('No Password Provided.');
3027
					break;
3028
				case 5:
3029
					$error = $err_str . gettext('No Hostname Provided.');
3030
					break;
3031
				case 6:
3032
					$error = $err_str . gettext('The Dynamic DNS Service provided is not yet supported.');
3033
					break;
3034
				case 7:
3035
					$error = $err_str . gettext('No Update URL Provided.');
3036
					break;
3037
				case 8:
3038
					$status = $err_str_r53 . gettext("Invalid ZoneID");
3039
					break;
3040
				case 9:
3041
					$status = $err_str_r53 . gettext("Invalid TTL");
3042
					break;
3043
				case 10:
3044
					$error = "phpDynDNS ({$this->_FQDN}): " . sprintf(gettext("No change in my IP address and/or %s days has not passed. Not updating dynamic DNS entry."), $this->_dnsMaxCacheAgeDays);
3045
					break;
3046
				default:
3047
					$error = $err_str . gettext('Unknown Response.');
3048
					/* FIXME: $data isn't in scope here */
3049
					/* $this->_debug($data); */
3050
					break;
3051
			}
3052
			$this->lastError = $error;
3053
			log_error($error);
3054
		}
3055

    
3056
		/*
3057
		 * Private Function (added 12 July 05) [beta]
3058
		 *   - Detect whether or not IP needs to be updated.
3059
		 *      | Written Specifically for pfSense (https://www.pfsense.org) may
3060
		 *      | work with other systems. pfSense base is FreeBSD.
3061
		 */
3062
		function _detectChange() {
3063
			if ($this->_dnsVerboseLog) {
3064
				log_error(sprintf(gettext('Dynamic DNS %1$s (%2$s): _detectChange() starting.'), $this->_dnsService, $this->_FQDN));
3065
			}
3066

    
3067
			$currentTime = time();
3068

    
3069
			$wan_ip = $this->_checkIP();
3070
			if ($wan_ip == 0) {
3071
				log_error(sprintf(gettext("Dynamic Dns (%s): Current WAN IP could not be determined, skipping update process."), $this->_FQDN));
3072
				return false;
3073
			}
3074
			$log_error = sprintf(gettext('Dynamic Dns (%1$s): Current WAN IP: %2$s'), $this->_FQDN, $wan_ip) . " ";
3075

    
3076
			if ($this->_useIPv6 == true) {
3077
				if (file_exists($this->_cacheFile_v6)) {
3078
					$contents = file_get_contents($this->_cacheFile_v6);
3079
					list($cacheIP, $cacheTime) = explode('|', $contents);
3080
					$this->_debug($cacheIP . '/' . $cacheTime);
3081
					$initial = false;
3082
					$log_error .= sprintf(gettext("Cached IPv6: %s"), $cacheIP);
3083
				} else {
3084
					$cacheIP = '::';
3085
					@file_put_contents($this->_cacheFile, "::|{$currentTime}");
3086
					$cacheTime = $currentTime;
3087
					$initial = true;
3088
					$log_error .= gettext("No Cached IPv6 found.");
3089
				}
3090
			} else {
3091
				if (file_exists($this->_cacheFile)) {
3092
					$contents = file_get_contents($this->_cacheFile);
3093
					list($cacheIP, $cacheTime) = explode('|', $contents);
3094
					$this->_debug($cacheIP . '/' . $cacheTime);
3095
					$initial = false;
3096
					$log_error .= sprintf(gettext("Cached IP: %s"), $cacheIP);
3097
				} else {
3098
					$cacheIP = '0.0.0.0';
3099
					@file_put_contents($this->_cacheFile, "0.0.0.0|{$currentTime}");
3100
					$cacheTime = $currentTime;
3101
					$initial = true;
3102
					$log_error .= gettext("No Cached IP found.");
3103
				}
3104
			}
3105
			if ($this->_dnsVerboseLog) {
3106
				log_error($log_error);
3107
			}
3108

    
3109
			// Convert seconds = days * hr/day * min/hr * sec/min
3110
			// We subtract 1 hour, so that when this code is executed few seconds
3111
			// before _dnsMaxCacheAgeDays has passed, then we still consider that
3112
			// a required number of days has passed.
3113
			$maxCacheAgeSecs = $this->_dnsMaxCacheAgeDays * 24 * 60 * 60 - 60 * 60;
3114

    
3115
			$needs_updating = FALSE;
3116
			/* lets determine if the item needs updating */
3117
			if ($cacheIP != $wan_ip) {
3118
				$needs_updating = true;
3119
				$update_reason = gettext("Dynamic Dns: cacheIP != wan_ip. Updating.") . " ";
3120
				$update_reason .= sprintf(gettext('Cached IP: %1$s WAN IP: %2$s'), $cacheIP, $wan_ip) . " ";
3121
			}
3122
			if (($currentTime - $cacheTime) > $maxCacheAgeSecs) {
3123
				$needs_updating = true;
3124
				$this->_forceUpdateNeeded = true;
3125
				$update_reason = sprintf(gettext("Dynamic Dns: More than %s days. Updating."), $this->_dnsMaxCacheAgeDays);
3126
				$update_reason .= " {$currentTime} - {$cacheTime} > {$maxCacheAgeSecs} ";
3127
			}
3128
			if ($initial == true) {
3129
				$needs_updating = true;
3130
				$update_reason .= gettext("Initial update.");
3131
			}
3132

    
3133
			/*   finally if we need updating then store the
3134
			 *   new cache value and return true
3135
			 */
3136
			if ($needs_updating == true) {
3137
				if ($this->_dnsVerboseLog) {
3138
					log_error("DynDns ({$this->_FQDN}): {$update_reason}");
3139
				}
3140
				return true;
3141
			}
3142

    
3143
			return false;
3144
		}
3145

    
3146
		/*
3147
		 * Private Function (added 16 July 05) [beta]
3148
		 *   - Writes debug information to a file.
3149
		 *   - This function is only called when a unknown response
3150
		 *   - status is returned from a DynDNS service provider.
3151
		 */
3152
		function _debug($data) {
3153
			global $g;
3154

    
3155
			if (!$g['debug']) {
3156
				return;
3157
			}
3158
			$string = date('m-d-y h:i:s') . ' - (' . $this->_debugID . ') - [' . $this->_dnsService . '] - ' . $data . "\n";
3159
			$file = fopen($this->_debugFile, 'a');
3160
			fwrite($file, $string);
3161
			fclose($file);
3162
		}
3163
		function _checkIP() {
3164
			if ($this->_dnsVerboseLog) {
3165
				log_error(sprintf(gettext('Dynamic DNS %1$s (%2$s): _checkIP() starting.'), $this->_dnsService, $this->_FQDN));
3166
			}
3167

    
3168
			if ($this->_useIPv6 == true) {
3169
				$ip_address = get_interface_ipv6($this->_if);
3170
				if (!is_ipaddrv6($ip_address)) {
3171
					return 0;
3172
				}
3173
			} else {
3174
				$ip_address = dyndnsCheckIP($this->_if);
3175
				if (!is_ipaddr($ip_address)) {
3176
					log_error(sprintf(gettext('Dynamic DNS %1$s (%2$s): IP address could not be extracted from Check IP Service'), $this->_dnsService, $this->_FQDN));
3177
					return 0;
3178
				} elseif (is_private_ip($ip_address)) {
3179
					log_error(sprintf(gettext('Dynamic DNS %1$s (%2$s): Public IP address could not be extracted from Check IP Service'), $this->_dnsService, $this->_FQDN));
3180
					return 0;
3181
				}
3182
			}
3183
			if ($this->_useIPv6 == false && is_private_ip(get_interface_ip($this->_if))) {
3184
				if ($this->_dnsVerboseLog) {
3185
					log_error(sprintf(gettext('Dynamic DNS %1$s (%2$s): %3$s extracted from Check IP Service'), $this->_dnsService, $this->_FQDN, $ip_address));
3186
				}
3187
			} else {
3188
				if ($this->_dnsVerboseLog) {
3189
					log_error(sprintf(gettext('Dynamic DNS %1$s (%2$s): %3$s extracted from local system.'), $this->_dnsService, $this->_FQDN, $ip_address));
3190
				}
3191
			}
3192
			$this->_dnsIP = $ip_address;
3193

    
3194
			return $ip_address;
3195
		}
3196

    
3197
	}
3198

    
3199
?>
(16-16/62)