Skip to content
Snippets Groups Projects
Commit 0dbe3e24 authored by Ferenc Schulcz's avatar Ferenc Schulcz
Browse files

Simple DynDNS functionality

parents
No related branches found
No related tags found
No related merge requests found
package-lock.json
node_modules/
config.json
\ No newline at end of file
const fs = require('fs');
let config = {};
function init() {
if (!fs.existsSync('config.json')) {
if (fs.existsSync('config.json.defaults')) {
fs.copyFileSync('config.json.defaults', 'config.json');
} else {
console.error("Cannot find config file at ./config.json and ./config.json.defaults is missing too");
process.exit(1);
}
}
config = JSON.parse(fs.readFileSync('config.json'));
}
module.exports = {
init: init,
config: () => { return config; }
}
\ No newline at end of file
{
"higherLevelDomain": "dyndns.mydomain.com",
"dbHost": "localhost"
}
\ No newline at end of file
var mongoose = require('mongoose');
const config = require('./config');
function init() {
const db_url = config.config().dbHost ? 'mongodb://' + config.config().dbHost + '/dyndns-server' : 'mongodb://localhost/dyndns-server';
mongoose.connect(db_url);
}
const Domain = mongoose.model('domain', {
name: String,
token: String,
ip: String
});
function domainExists(domain) {
return new Promise((resolve, reject) => {
Domain.exists({ name: domain })
.catch((err) => resolve(true))
.then((result) => resolve(result));
});
}
function registerDomain(domain, token) {
return new Promise((resolve, reject) => {
const record = new Domain({
name: domain,
token: token,
ip: '0.0.0.0'
});
record.save()
.catch((err) => reject())
.then(() => resolve());
});
}
function getDomain(token) {
return new Promise((resolve, reject) => {
Domain.findOne({ token: token })
.catch((err) => reject())
.then((domain) => resolve(domain));
});
}
function updateDomain(domain, token, ip) {
return new Promise((resolve, reject) => {
Domain.updateOne({ token: token }, { name: domain, token: token, ip: ip })
.catch((err) => reject())
.then((d) => resolve());
})
}
module.exports = {
init: init,
domainExists: domainExists,
registerDomain: registerDomain,
getDomain: getDomain,
updateDomain: updateDomain
}
\ No newline at end of file
index.js 0 → 100644
const express = require('express');
const app = express();
const config = require('./config');
config.init();
const db = require('./database');
db.init();
const registerMW = require('./middleware/registerMW');
const replyMW = require('./middleware/replyMW');
const updateMW = require('./middleware/updateMW');
// just redirects to main webpage
app.get('/', (req, res, next) => { return res.redirect('https://sferi.hu/') });
// register a new dyndns record
app.get('/register', registerMW(), replyMW());
// update an existing dyndns record
app.get('/update', updateMW(), replyMW());
const server_port = 3000;
const server_domain = '0.0.0.0';
app.listen(server_port, server_domain, function () {
console.log(`This server is listening on http://${server_domain}:${server_port}`);
});
\ No newline at end of file
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const config = require('./config');
async function updateRecord(domain, ip) {
await exec("/usr/sbin/knotc zone-begin " + config.config().higherLevelDomain);
await exec("/usr/sbin/knotc zone-unset " + config.config().higherLevelDomain + " " + domain);
await exec("/usr/sbin/knotc zone-set " + config.config().higherLevelDomain + " " + domain + " 60 A " + ip);
await exec("/usr/sbin/knotc zone-commit " + config.config().higherLevelDomain);
}
module.exports = {
updateRecord: updateRecord
}
\ No newline at end of file
const { randomInt } = require('crypto');
const md5 = require('js-md5');
const db = require('../database');
const config = require('../config');
// Will register a new DynDNS domain name.
//
// `domain` URL parameter: the first part of the domain name as <firstpart>.<higher.level.domain>
// Must only contain English letters, numbers and dash (-)
//
// on success, returns JSON:
// {
// domainName: "<domain.dyndns.example.com>",
// token: "<token>",
// message: "DNS record is not served until first DynDNS update."
// }
//
// on error, sets correct status code and returns JSON:
// {
// message: "<Error message.>"
// }
module.exports = function () {
return function (req, res, next) {
if (!('domain' in req.query)) {
res.locals.statuscode = 400;
res.locals.answer = { message: "Register what?" };
next();
return;
}
const domain = req.query.domain;
const format = /^[a-zA-Z0-9\-]*$/;
if (!format.test(domain)) {
res.locals.statuscode = 400;
res.locals.answer = { message: "Domain name must only contain English letters, numbers and dash (-)." };
next();
return;
}
return db.domainExists(domain).then((exists) => {
if (exists) {
res.locals.statuscode = 400;
res.locals.answer = { message: "This domain is already in use." };
next();
return;
} else {
const token = md5(domain + randomInt(2 ** 31 - 1) + randomInt(2 ** 31 - 1)).toString('hex');
return db.registerDomain(domain, token)
.catch(() => {
res.locals.statuscode = 500;
res.locals.answer = { message: "Could not save in database." };
next();
return;
})
.then(() => {
res.locals.statuscode = 201;
res.locals.answer = {
domainName: domain + "." + config.config().higherLevelDomain,
token: token,
message: "DNS record is not served until first DynDNS update."
};
console.log("Registered " + domain);
next();
return;
})
}
})
}
}
\ No newline at end of file
// Will just send a JSON reply with an HTTP status code.
module.exports = function () {
return function (req, res, next) {
res.status(res.locals.statuscode);
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(res.locals.answer));
}
}
\ No newline at end of file
const db = require('../database');
const knotdns = require('../knotdns');
// Will update an existing DNS record
//
// `token` URL parameter: the token associated with the record
// `ip` URL parameter: the new IP
//
// returns JSON:
// { message: "<Status message>" }
module.exports = function () {
return function (req, res, next) {
if (!('token' in req.query)) {
res.locals.statuscode = 400;
res.locals.answer = { message: "Update what?" };
next();
return;
}
if (!('ip' in req.query)) {
res.locals.statuscode = 400;
res.locals.answer = { message: "Did not receive new IP." };
next();
return;
}
const token = req.query.token;
const ip = req.query.ip;
return db.getDomain(token)
.catch(() => {
res.locals.statuscode = 500;
res.locals.answer = { message: "Could not save in database." };
next();
return;
})
.then((record) => {
db.updateDomain(record.name, token, ip).catch(() => {
res.locals.statuscode = 500;
res.locals.answer = { message: "Could not save in database." };
next();
return;
}).then(() => {
knotdns.updateRecord(record.name, ip).then(() => {
console.log("Updated record " + record.name + " from " + record.ip + " to " + ip);
res.locals.statuscode = 200;
res.locals.answer = { message: "Updated." };
next();
return;
})
})
})
}
}
\ No newline at end of file
{
"name": "dyndns-server",
"version": "1.0.0",
"description": "Dynamic DNS server that builds on Knot",
"main": "index.js",
"scripts": {
"test": "echo 'Not implemented yet.'"
},
"repository": {
"type": "git",
"url": "https://git.sch.bme.hu/schulczf/dyndns-server"
},
"author": "Ferenc Schulcz",
"license": "GPL-3.0-or-later",
"dependencies": {
"express": "^4.18.2",
"js-md5": "^0.8.3",
"mongoose": "^8.1.1"
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment