First commit

This commit is contained in:
James Coleman 2016-05-22 16:14:17 -05:00
commit a61578a02e
6 changed files with 420 additions and 0 deletions

12
License.txt Executable file
View File

@ -0,0 +1,12 @@
Copyright (c) 2016, Mr. Gecko's Media (James Coleman)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

BIN
databases/main.db Normal file

Binary file not shown.

108
dbSQLITE.php Normal file
View File

@ -0,0 +1,108 @@
<?php
//
// Copyright (c) 2016 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/
//
// Permission to use, copy, modify, and/or distribute this software for any purpose
// with or without fee is hereby granted, provided that the above copyright notice
// and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
// OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
// DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
// ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
function connectToDatabase() {
global $_MGM;
if (isset($_MGM['DBConnection'])) closeDatabase();
$_MGM['DBConnection'] = NULL;
$_MGM['DBConnection'] = new PDO("sqlite:".$_MGM['DBName']);
$_MGM['DBConnection']->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
if ($_MGM['DBPersistent'])
$_MGM['DBConnection']->setAttribute(PDO::ATTR_PERSISTENT, TRUE);
if ($_MGM['DBConnection']==NULL) error("Database Connection Failed");
}
function closeDatabase() {
global $_MGM;
if (isset($_MGM['DBConnection'])) {
$_MGM['DBConnection'] = NULL;
}
}
function escapeString($theString) {
global $_MGM;
return $_MGM['DBConnection']->quote($theString);
}
function quoteObject($theObject) {
global $_MGM;
if (is_null($theObject)) {
return "NULL";
} else if (is_string($theObject)) {
return escapeString($theObject);
} else if (is_float($theObject) || is_integer($theObject)) {
return $theObject;
} else if (is_bool($theObject)) {
return ($theObject ? 1 : 0);
}
return "NULL";
}
function databaseQuery($format) {
global $_MGM;
$result = NULL;
try {
if (isset($_MGM['DBConnection'])) {
$args = func_get_args();
array_shift($args);
$args = array_map("quoteObject", $args);
$query = vsprintf($format, $args);
$result = $_MGM['DBConnection']->query($query);
}
//if ($result==NULL) error("Failed to run query on database");
} catch (Exception $e) {
//echo $e->getMessage()."<br />\n";
//error("Failed to run query on database");
}
return $result;
}
function databaseRowCount($theResult) {
global $_MGM;
if ($theResult==NULL)
return 0;
return $theResult->rowCount();
}
function databaseFieldCount($theResult) {
global $_MGM;
if ($theResult==NULL)
return 0;
return $theResult->columnCount();
}
function databaseLastID() {
global $_MGM;
$result = 0;
if (isset($_MGM['DBConnection'])) {
$result = $_MGM['DBConnection']->lastInsertId();
}
return $result;
}
function databaseFetch($theResult) {
global $_MGM;
return $theResult->fetch();
}
function databaseFetchNum($theResult) {
global $_MGM;
return $theResult->fetch(PDO::FETCH_NUM);
}
function databaseFetchAssoc($theResult) {
global $_MGM;
return $theResult->fetch(PDO::FETCH_ASSOC);
}
function databaseResultSeek($theResult, $theLocation) {
global $_MGM;
return false;
}
function databaseFreeResult($theResult) {
global $_MGM;
$theResult = NULL;
}
?>

192
index.php Normal file
View File

@ -0,0 +1,192 @@
<?php
//
// Copyright (c) 2016 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/
//
// Permission to use, copy, modify, and/or distribute this software for any purpose
// with or without fee is hereby granted, provided that the above copyright notice
// and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
// OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
// DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
// ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING & ~E_STRICT);
function error($error) {
echo $error;
exit();
}
$_MGM = array();
$_MGM['version'] = "1";
$_MGM['title'] = "GeckoDNS";
$_MGM['author'] = "Mr. Gecko";
$_MGM['DBType'] = "SQLITE"; // MYSQL, POSTGRESQL, SQLITE.
$_MGM['DBPersistent'] = false;
$_MGM['DBHost'] = "localhost";
$_MGM['DBUser'] = "";
$_MGM['DBPassword'] = "";
$_MGM['DBName'] = "databases/main.db"; // File location for SQLite.
$_MGM['DBPort'] = 0; // 3306 = MySQL Default, 5432 = PostgreSQL Default.
$_MGM['DBPrefix'] = "";
$_MGM['adminEmail'] = "default@domain.com";
require_once("db{$_MGM['DBType']}.php");
putenv("TZ=US/Central");
$_MGM['time'] = time();
if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR']!="")
$_MGM['ip'] = $_SERVER['REMOTE_ADDR'];
/*if (isset($_SERVER['HTTP_PC_REMOTE_ADDR']) && $_SERVER['HTTP_PC_REMOTE_ADDR']!="")
$_MGM['ip'] = $_SERVER['HTTP_PC_REMOTE_ADDR'];
if (isset($_SERVER['HTTP_CLIENT_IP']) && $_SERVER['HTTP_CLIENT_IP']!="")
$_MGM['ip'] = $_SERVER['HTTP_CLIENT_IP'];
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR']!="")
$_MGM['ip'] = $_SERVER['HTTP_X_FORWARDED_FOR'];*/
$_MGM['nsupdatePath'] = "/usr/local/bin/nsupdate";
$_MGM['defaultTTL'] = 1800;
$_MGM['debug'] = false;
function hashPassword($password, $salt) {
return hash_pbkdf2("sha512", $password, $salt, 1000);
}
if (isset($_REQUEST['passwd'])) {
if (isset($_REQUEST['password'])) {
$salt = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM);
$password = hashPassword($_REQUEST['password'], $salt);
echo bin2hex($salt).$password;
exit();
}
?>
<html>
<head>
<title>Generate password for storage in <?=$_MGM['title']?> database.</title>
</head>
<body>
<form action="?passwd" method="post">
<input type="text" name="password" placeholder="Password" autocomplete="off" />
<input type="submit" value="Generate Password" />
</form>
</body>
<?
exit();
}
connectToDatabase();
if (isset($_REQUEST['username']) && isset($_REQUEST['password'])) {
$result = databaseQuery("SELECT * FROM users WHERE username=%s AND level!=0", $_REQUEST['username']);
$user = databaseFetchAssoc($result);
if ($user!=NULL) {
$salt = hex2bin(substr($user['password'], 0, 32));
$password = substr($user['password'], 32);
if ($password==hashPassword($_REQUEST['password'], $salt)) {
$_MGM['user'] = $user;
}
}
}
header("Content-Type: text/plain");
if (!isset($_MGM['user'])) {
echo "Invalid Credentials";
closeDatabase();
exit();
}
databaseQuery("UPDATE users SET lastmessage=%s WHERE username=%s", $_MGM['time'], $_MGM['user']['username']);
$dblast = "";
$nsrecord = "";
if (filter_var($_MGM['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$dblast = "lastv4";
$nsrecord = "A";
} else if (filter_var($_MGM['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$dblast = "lastv6";
$nsrecord = "AAAA";
}
if ($dblast=="") {
echo "Invalid IP Address Detected";
closeDatabase();
exit();
}
if ($_MGM['user'][$dblast]!=$_MGM['ip']) {
echo "New IP: ".$_MGM['ip']."\n\n";
$updateFile = tempnam(sys_get_temp_dir(), 'GeckoDNS');
$fh = fopen($updateFile, 'w');
fwrite($fh, "server ".$_MGM['user']['server']."\n");
fwrite($fh, "debug yes\n");
fwrite($fh, "zone ".$_MGM['user']['zone']."\n");
$hosts = explode(",", $_MGM['user']['hosts']);
foreach ($hosts as $host) {
$suffix = substr($host, strlen($host)-strlen($_MGM['user']['zone']));
if ($suffix!=$_MGM['user']['zone'] && !preg_match('/^[a-zA-Z0-9.-*]+$/', $host)) {
echo "Host: ".$host."\n\n";
echo "Invalid Configuration";
fclose($fh);
unlink($updateFile);
closeDatabase();
exit();
}
fwrite($fh, "update delete ".$host." ".$nsrecord."\n");
fwrite($fh, "update add ".$host." ".$_MGM['defaultTTL']." ".$nsrecord." ".$_MGM['ip']."\n");
}
fwrite($fh, "show\n");
fwrite($fh, "send\n");
fclose($fh);
$command = $_MGM['nsupdatePath']." -k ".escapeshellarg("keys/".$_MGM['user']['key'])." ".$updateFile;
$result = exec($command." 2>&1", $output);
if ($_MGM['debug']) {
echo "Output: \n";
}
$foundReply = false;
$status = 0;
foreach ($output as $line) {
if ($_MGM['debug']) {
echo $line."\n";
}
if ($foundReply && $status==0) {
if (strpos($line, "status: NOERROR")!==false) {
$status = 1;
} else {
$status = 2;
}
}
if (strpos($line, "Reply from update query:")!==false) {
$foundReply = true;
}
}
if ($status==1) {
echo "\nUpdate Successful";
databaseQuery("UPDATE users SET lastupdate=%s, $dblast=%s WHERE username=%s", $_MGM['time'], $_MGM['ip'], $_MGM['user']['username']);
} else {
if ($_MGM['debug']) {
echo "\nCommand: ".$command."\n";
echo "\nUpdate File:\n";
readfile($updateFile);
}
echo "\nUpdate Unsuccessful";
}
unlink($updateFile);
} else {
echo "No Update";
}
closeDatabase();
?>

85
readme.md Normal file
View File

@ -0,0 +1,85 @@
#GeckoDNS
I needed a Dynamic DNS service of my own, so I wrote a quick one. This uses nsupdate with keys to update DNS records with current IP addresses. It uses simple username and password authentication over http. If you want security, you should run updates over TLS. The backend authenticates updates with the DNS server via TSGN signing.
##Instructions
###Modify `index.php` to your liking by making sure values are set right.
Mainly verify nsupdatePath is correct.
###Configure Bind9 to allow updating for the zone you need Dynamic DNS for.
Change directory to the location where your named.conf is located.
Generate a key for updating.
```
# dnssec-keygen -r /dev/urandom -a HMAC-MD5 -b 512 -n HOST example.com
Kexample.com.+157+42278
```
Read the file outputted in a text editor and copy the key value.
```
# cat Kexample.com.+157+42278.private
Private-key-format: v1.3
Algorithm: 157 (HMAC_MD5)
Key: pbrxTbsWVoE8ys0529iKpLUHQzH389rmx39yxvsgvltVABGzR4hyHzycJnlFVMuBpPJI8qO8awma5PCEfDgu/Q==
Bits: AAA=
Created: 20160522202756
Publish: 20160522202756
Activate: 20160522202756
```
Place the key in a new file in the following format.
```
# nano example.com.key
key "example.com." {
algorithm hmac-md5;
secret "pbrxTbsWVoE8ys0529iKpLUHQzH389rmx39yxvsgvltVABGzR4hyHzycJnlFVMuBpPJI8qO8awma5PCEfDgu/Q==";
};
```
Edit named.conf and add the following:
```
# nano named.conf
include "example.com.key";
zone "example.com" {
type master;
file "/var/named/example.com.hosts";
allow-update { key "example.com."; };
};
```
The important part is `allow-update { key "example.com."; };` which allows anything which signs a message with that key to update the zone.
###Upload GeckoDNS to a web server which has access to running nsupdate.
###Add your zone key file in the `keys` folder of GeckoDNS.
###Visit https://website.com/GeckoDNS/?passwd to generate a hash of your password for updating dns.
###Create user account with details.
```
# sqlite3 databases/main.db
sqlite> INSERT INTO "users" VALUES('username','7589bc703af770d39785d714c1e32ec49145031885a8ac4a2e5f5001b9f2ec85ef274fd962e88ab0d85ff55aae90bb3be873c347eb75a1099c051d04569574fac24dbb071da6b7868182f0715885145f',1,'127.0.0.1','example.com.key','example.com.','example.com.,subdomain.example.com.',NULL,NULL,NULL,NULL);
```
###Configure nginx (or apache) to block access to private folders.
Nginx config, I'm not writing Apache config. Sorry.
```
location ~* /GeckoDNS/(databases|keys) {
deny all;
}
```
###Copy and configure cron `updateGeckoDNS.sh` to have proper username/password/update urls on computer you want to update the DNS.
###Configure cronjob on the computer you want to update the DNS.
```
# crontab -e
*/30 * * * * /bin/bash /usr/local/bin/updateGeckoDNS.sh >/dev/null 2>&1
```
##Final words
I do not know if I would ever update this from this point, however there are some things in place so I can add on to the system. Maybe in the future it will be designed more of a service than a personal Dynamic DNS system. A good thing to change would be to separate zones from users to allow multiple zones per account and custom TTL per host.

23
updateGeckoDNS.sh Normal file
View File

@ -0,0 +1,23 @@
#/bin/bash
#Username and password.
username="username"
password="password"
#The ipv4 subdomain needs to be created if you have both IPv6 and IPv4 on the website.
#The subdomain isn't a requirement.
#You can do https://website.com/GeckoDNS/ if you don't have the subdomain made.
ipv4update="https://ipv4.website.com/GeckoDNS/"
#Lave blank if you do not do IPv6.
ipv6update="https://ipv6.website.com/GeckoDNS/"
if [ ! -z "$ipv4update" ]; then
curl -4 "$ipv4update?username=$username&password=$password"
echo ""
fi
if [ ! -z "$ipv6update" ]; then
curl -6 "$ipv6update?username=$username&password=$password"
echo ""
fi