Skip to content

Commit dedc04d

Browse files
committed
Initial commit
0 parents  commit dedc04d

File tree

7 files changed

+473
-0
lines changed

7 files changed

+473
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
config.inc.php

Config.class.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
/** Prevent direct access to this file */
4+
if (defined('APPLICATION') === false) {
5+
die('Direct access not permitted!');
6+
}
7+
8+
class Config
9+
{
10+
/**
11+
* Name of the file this class represents.
12+
* @var string
13+
*/
14+
private $filename = '';
15+
16+
/**
17+
* Entire configuration array this class represents.
18+
* @var array
19+
*/
20+
private $config = [];
21+
22+
/**
23+
* Creates a new instance of this class.
24+
* @param string $filename Path of the file to use.
25+
* @return void
26+
*/
27+
public function __construct(string $filename)
28+
{
29+
$this->filename = $filename;
30+
$this->config = $this->readConfig($filename);
31+
}
32+
33+
/**
34+
* Returns a specific item from the array by dot notation.
35+
* @param string $path Dot notated path to the array item.
36+
* @param mixed $default Default value if the result is null.
37+
* @return mixed The value of that item in the array.
38+
*/
39+
public function get($path, $default = null)
40+
{
41+
$array = $this->config;
42+
$parts = explode('.', $path);
43+
44+
foreach ($parts as $part) {
45+
if (isset($array[$part]) === false) {
46+
return $default;
47+
}
48+
49+
$array = $array[$part];
50+
}
51+
52+
return $array ?? $default;
53+
}
54+
55+
/**
56+
* Returns the content of the specified file.
57+
* @param string $filename Path of the file to load.
58+
* @return mixed File contents.
59+
*/
60+
private function readConfig(string $filename)
61+
{
62+
return include $filename;
63+
}
64+
}

DDNSProvider.class.php

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
3+
/** Prevent direct access to this file */
4+
if (defined('APPLICATION') === false) {
5+
die('Direct access not permitted!');
6+
}
7+
8+
/** Require dependencies */
9+
require_once 'FroxlorAPI.class.php';
10+
11+
class DDNSProvider
12+
{
13+
/** @var Config */
14+
private $config;
15+
16+
/** @var FroxlorAPI */
17+
private $api;
18+
19+
/** @var string */
20+
private $domainName;
21+
22+
/** @var int */
23+
private $domainTtl;
24+
25+
/**
26+
* Creates a new instance of this class.
27+
* @param Config $config Configuration to use.
28+
* @return void
29+
*/
30+
public function __construct(Config $config)
31+
{
32+
$this->config = $config;
33+
$this->api = new FroxlorAPI(
34+
$this->config->get('froxlor.endpoint'),
35+
$this->config->get('froxlor.api_key'),
36+
$this->config->get('froxlor.api_secret')
37+
);
38+
}
39+
40+
public function use(string $user, string $token, string $domain): bool
41+
{
42+
$users = array_keys($this->config->get('users'));
43+
44+
/** Check if the user exists. */
45+
if (in_array($user, $users) === false) {
46+
return false;
47+
}
48+
49+
/** Read user properties */
50+
$userToken = $this->config->get("users.${user}.token");
51+
$userDomains = $this->config->get("users.${user}.domains");
52+
53+
/** Verify token */
54+
if (strcmp($userToken, $token) !== 0) {
55+
return false;
56+
}
57+
58+
/** Verify domain */
59+
if (in_array($domain, array_keys($userDomains)) === false) {
60+
return false;
61+
}
62+
63+
$this->domainName = $domain;
64+
$this->domainTtl = $userDomains[$domain];
65+
return true;
66+
}
67+
68+
public function updateIp(string $host, string $ip)
69+
{
70+
if (isset($this->domainName) === false) {
71+
die('ERROR: Make sure to select a zone before trying to update the IP address.');
72+
}
73+
74+
/** Get current entry IDs and delete them */
75+
$entryIds = $this->getCurrentEntry($host);
76+
foreach ($entryIds as $id) {
77+
$this->api->request('DomainZones.delete', ['entry_id' => $id, 'domainname' => $this->domainName]);
78+
}
79+
80+
/** Create the new record with the current ID */
81+
$this->api->request('DomainZones.add', [
82+
'domainname' => $this->domainName,
83+
'record' => $host,
84+
'type' => 'A',
85+
'content' => $ip,
86+
'ttl' => $this->domainTtl
87+
]);
88+
}
89+
90+
/**
91+
* Finds all A records for the current host and returns their IDs.
92+
* @param string $host Host to check for.
93+
* @return array Array of all IDs.
94+
*/
95+
private function getCurrentEntry(string $host): array
96+
{
97+
$this->api->request('DomainZones.listing', ['domainname' => $this->domainName]);
98+
99+
/** Check if an error occoured */
100+
if (empty($this->api->getLastError()) === false) {
101+
return [];
102+
}
103+
104+
$response = $this->api->getLastResponse();
105+
106+
/** If no entry exists, simply skip as there is nothing to query for */
107+
$count = $response['count'];
108+
if ($count === 0) {
109+
return [];
110+
}
111+
112+
$entries = $response['list'];
113+
$ids = [];
114+
foreach ($entries as $entry) {
115+
if (strcmp($host, $entry['record']) !== 0 || strcmp('A', $entry['type']) !== 0) {
116+
continue;
117+
}
118+
119+
$ids[] = $entry['id'];
120+
}
121+
122+
return $ids;
123+
}
124+
}

FroxlorAPI.class.php

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
<?php
2+
3+
/** Prevent direct access to this file */
4+
if (defined('APPLICATION') === false) {
5+
die('Direct access not permitted!');
6+
}
7+
8+
class FroxlorAPI
9+
{
10+
11+
/**
12+
* URL to api.php of your froxlor installation
13+
*
14+
* @var string
15+
*/
16+
private $host = "";
17+
18+
/**
19+
* your api-key
20+
*
21+
* @var string
22+
*/
23+
private $api_key = "";
24+
25+
/**
26+
* your api-secret
27+
*
28+
* @var string
29+
*/
30+
private $api_secret = "";
31+
32+
/**
33+
* last cURL error message
34+
*
35+
* @var string
36+
*/
37+
private $last_error = "";
38+
39+
/**
40+
* last response header received
41+
*
42+
* @var array
43+
*/
44+
private $last_header = array();
45+
46+
/**
47+
* last response data received
48+
*
49+
* @var array
50+
*/
51+
private $last_body = array();
52+
53+
/**
54+
* create FroxlorAPI object
55+
*
56+
* @param string $host
57+
* URL to api.php of your froxlor installation
58+
* @param string $api_key
59+
* your api-key
60+
* @param string $api_secret
61+
* your api-secret
62+
*
63+
* @return FroxlorAPI
64+
*/
65+
public function __construct(string $host, string $api_key, string $api_secret)
66+
{
67+
$this->host = $host;
68+
$this->api_key = $api_key;
69+
$this->api_secret = $api_secret;
70+
}
71+
72+
/**
73+
* send request to froxlor api
74+
*
75+
* @param string $command
76+
* @param array $params
77+
*
78+
* @return FroxlorAPI
79+
*/
80+
public function request(string $command, array $params = array()): FroxlorAPI
81+
{
82+
// build request array
83+
$request = [
84+
'header' => [
85+
'apikey' => $this->api_key,
86+
'secret' => $this->api_secret
87+
],
88+
'body' => [
89+
'command' => $command
90+
]
91+
];
92+
93+
// add parameter to request-body if any
94+
if (!empty($params)) {
95+
$request['body']['params'] = $params;
96+
}
97+
98+
// reset last data
99+
$this->last_header = array();
100+
$this->last_body = array();
101+
102+
// send actual request
103+
$response = $this->requestCurl(json_encode($request));
104+
105+
// decode response
106+
$resp = json_decode($response[1], true);
107+
// set body to data-part of response
108+
$this->last_body = $resp['data'];
109+
// set header of response
110+
$this->last_header = [
111+
'status' => $resp['status'],
112+
'status_message' => $resp['status_message']
113+
];
114+
115+
// check for error in api response
116+
if (isset($this->last_header['status']) && $this->last_header['status'] >= 400) {
117+
// set last-error message
118+
$this->last_error .= "[" . $this->last_header['status'] . "] " . $this->last_header['status_message'];
119+
}
120+
121+
return $this;
122+
}
123+
124+
/**
125+
* returns last response header
126+
*
127+
* @return array status|status_message
128+
*/
129+
public function getLastHeader(): array
130+
{
131+
return $this->last_header;
132+
}
133+
134+
/**
135+
* returns last response data
136+
*
137+
* @return array
138+
*/
139+
public function getLastResponse(): array
140+
{
141+
if (!empty($this->getLastError())) {
142+
// nothing is returned when the last call
143+
// was not successful
144+
return [];
145+
}
146+
return $this->last_body;
147+
}
148+
149+
/**
150+
* return last known error message
151+
*
152+
* @return string
153+
*/
154+
public function getLastError(): string
155+
{
156+
return $this->last_error;
157+
}
158+
159+
/**
160+
* send cURL request to api
161+
*
162+
* @param string $data
163+
* json array
164+
*
165+
* @return array header|body
166+
*/
167+
private function requestCurl(string $data): array
168+
{
169+
// reset last error message
170+
$this->last_error = "";
171+
172+
$ch = curl_init($this->host);
173+
curl_setopt($ch, CURLOPT_POST, 1);
174+
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
175+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
176+
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
177+
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
178+
'Content-type: application/json'
179+
));
180+
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
181+
curl_setopt($ch, CURLOPT_VERBOSE, true);
182+
curl_setopt($ch, CURLOPT_HEADER, 1);
183+
$verbose = fopen('php://temp', 'w+');
184+
curl_setopt($ch, CURLOPT_STDERR, $verbose);
185+
186+
if (!$data = curl_exec($ch)) {
187+
$this->last_error = 'Curl execution error: ' . curl_error($ch) . "\n";
188+
rewind($verbose);
189+
$verboseLog = stream_get_contents($verbose);
190+
$this->last_error .= "Verbose information: " . htmlspecialchars($verboseLog) . "\n";
191+
}
192+
193+
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
194+
$header = substr($data, 0, $header_size);
195+
$body = substr($data, $header_size);
196+
197+
curl_close($ch);
198+
return array(
199+
$header,
200+
$body
201+
);
202+
}
203+
}

0 commit comments

Comments
 (0)