initial commit

This commit is contained in:
Evert Prants 2019-02-05 18:27:36 +02:00
commit ed45048920
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
10 changed files with 502 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/*/**/config.json
/node_modules/
/package-lock.json

1
applications/distrib-ipfs/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.db

View File

@ -0,0 +1,48 @@
const express = require('express')
const path = require('path')
const sqlite = require('sqlite')
const crypto = require('crypto')
const router = express.Router()
const cfgLoader = require(path.join('..', '..', 'config-loader'))(path.join(__dirname, 'config.json'), {
database: 'shortened.db',
gateway: 'https://distributed.icynet.eu',
tokens: {}
})
async function init () {
let config = await cfgLoader
const dbPromise = Promise.resolve()
.then(() => sqlite.open(path.join(__dirname, config.database), { Promise, cache: true }))
.then(db => db.migrate({ migrationsPath: path.join(__dirname, 'migrations') }))
router.get('/:hash', async (req, res, next) => {
let db = await dbPromise
let translation = await db.get('SELECT * FROM Translation WHERE translation = ?', req.params.hash)
if (!translation) return res.status(404).end()
res.header('Cache-Control', 'max-age=' + 7 * 24 * 60 * 60 * 1000)
res.redirect(config.gateway + '/ipfs/' + translation.file_hash)
})
router.post('/publish', async (req, res, next) => {
let token = req.header('token') || req.body.token
if (!token || !config.tokens[token]) return res.status(402).send('Forbidden')
let baseurl = config.tokens[token]
let hash = req.query.hash || req.body.hash
let filename = req.query.filename || req.body.filename
if (!hash) return res.status(400).send('Invalid request: missing IPFS hash')
if (!filename) filename = crypto.randomBytes(8).toString('hex')
let db = await dbPromise
await db.run('INSERT INTO Translation (translation,file_hash,timeat) VALUES (?,?,?)', filename, hash, new Date())
res.send(baseurl + filename)
})
return router
}
module.exports = init

View File

@ -0,0 +1,10 @@
-- Up
CREATE TABLE Translation (
translation TEXT,
file_hash TEXT,
timeat TEXT
);
-- Down
DROP TABLE Translation;

View File

@ -0,0 +1,282 @@
const express = require('express')
const util = require('util')
const exec = util.promisify(require('child_process').exec)
const path = require('path')
const fs = require('fs')
const fsp = fs.promises
const strs = [
'%sdmn IN A %ip',
'%sdmn IN AAAA %ip'
]
const router = express.Router()
const cfgLoader = require(path.join('..', '..', 'config-loader'))(path.join(__dirname, 'config.json'), {
named: '/var/named/',
tokens: {}
})
let memcache = {}
function fmtip (i, sd, ip) {
return strs[i].replace('%sdmn', sd).replace('%ip', ip)
}
async function updateSerial (zonefile, contents) {
let returnContents = contents != null
if (!contents) {
contents = await fsp.readFile(zonefile, {encoding: 'utf8'})
}
// Split the file into lines
let lines = contents.split('\n')
// Find 'serial'
let prv = false
let found = false
for (let i in lines) {
if (found) break
let line = lines[i]
if (line.indexOf('SOA') !== -1) {
prv = true
continue
}
if (prv && line.toLowerCase().indexOf('serial') !== -1) {
// Ladies and gentlemen, we got 'em
found = true
line = line.replace(/\d+/, Math.floor(Date.now() / 1000))
lines[i] = line
break
}
if (prv) {
if (line.indexOf(',') === -1 && line.indexOf('(') === -1
&& line.indexOf(')') === -1 && line.indexOf('IN') === -1) {
found = true
line = line.replace(/\d+/, Math.floor(Date.now() / 1000))
lines[i] = line
break
} else {
// Just give up, not in this script's readability scope..
break
}
}
prv = false
}
contents = lines.join('\n')
if (!found) return contents
if (returnContents) return contents
await fsp.writeFile(zonefile, contents)
}
async function updateZone (cfg, v4, v6) {
let zone = cfg.domain + '.zone'
let zfile = path.join(cfg.root, zone)
await fsp.access(zfile, fs.constants.F_OK | fs.constants.W_OK)
if (!memcache[cfg.token]) {
memcache[cfg.token] = {}
} else if (memcache[cfg.token]['v4'] === v4 && memcache[cfg.token]['v6'] === v6) {
// Don't update when it's identical
return false
}
memcache[cfg.token].v6 = v6
memcache[cfg.token].v4 = v4
// If subdomain exists, use that file instead and update serial on primary
if (cfg.subdomain && cfg.subdomain !== '@') {
let zone2 = cfg.subdomain + '.' + zone
let zfile2 = path.join(cfg.root, zone2)
await fsp.access(zfile2, fs.constants.F_OK | fs.constants.W_OK)
let file = '; GENERATED BY DYNDNS'
if (v4) {
file += '\n' + fmtip(0, cfg.subdomain, v4)
}
if (v6) {
file += '\n' + fmtip(1, cfg.subdomain, v6)
}
await fsp.writeFile(zfile2, file)
await updateSerial(zfile)
await exec('rndc reload ' + cfg.domain)
return true
}
// Subdomain not set, just update '@' target in main zone file
let zoneFile = await fsp.readFile(zfile, {encoding: 'utf8'})
let atlines = []
let lines = zoneFile.split('\n')
for (let i in lines) {
let line = lines[i]
if (line.indexOf('@') === 0 && line.indexOf('SOA') === -1 && line.indexOf('A') !== -1) {
atlines.push(i)
}
}
if (atlines.length === 0) throw new Error('I dont know what to do with this zone file.')
let set6 = false
let set4 = false
for (let j in atlines) {
let line = lines[atlines[j]]
if (line.indexOf('AAAA') !== -1) {
if (set6 || v6 === null) {
lines.splice(atlines[j], 1)
continue
}
if (v6) {
lines[atlines[j]] = fmtip(1, '@', v6)
set6 = true
} else if (v4 && atlines.length !== 1) {
lines[atlines[j]] = fmtip(0, '@', v4)
set4 = true
}
if (v4 && atlines.length === 1) {
lines.splice(atlines[j], 0, fmtip(0, '@', v4))
set4 = true
}
} else {
if (set4 || v4 === null) {
lines.splice(atlines[j], 1)
continue
}
if (v4) {
lines[atlines[j]] = fmtip(0, '@', v4)
set4 = true
} else if (v6 && atlines.length !== 1) {
lines[atlines[j]] = fmtip(1, '@', v6)
set6 = true
}
if (v6 && atlines.length === 1) {
lines.splice(atlines[j], 0, fmtip(1, '@', v6))
set6 = true
}
}
}
zoneFile = await updateSerial(null, lines.join('\n'))
if (!zoneFile) throw new Error('I dont know what to do with this zone file.')
await fsp.writeFile(zfile, zoneFile)
await exec('rndc reload ' + cfg.domain)
}
function validv4 (ipaddress) {
if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipaddress)) {
return true
}
return false
}
function validv6 (value) {
// See https://blogs.msdn.microsoft.com/oldnewthing/20060522-08/?p=31113 and
// https://4sysops.com/archives/ipv6-tutorial-part-4-ipv6-address-syntax/
const components = value.split(':')
if (components.length < 2 || components.length > 8) {
return false
}
if (components[0] !== '' || components[1] !== '') {
// Address does not begin with a zero compression ("::")
if (!components[0].match(/^[\da-f]{1,4}/i)) {
// Component must contain 1-4 hex characters
return false
}
}
let numberOfZeroCompressions = 0
for (let i = 1; i < components.length; ++i) {
if (components[i] === '') {
// We're inside a zero compression ("::")
++numberOfZeroCompressions
if (numberOfZeroCompressions > 1) {
// Zero compression can only occur once in an address
return false
}
continue
}
if (!components[i].match(/^[\da-f]{1,4}/i)) {
// Component must contain 1-4 hex characters
return false
}
}
return true
}
async function init () {
let config = await cfgLoader
await fsp.access(config.named, fs.constants.F_OK)
router.post('/', async (req, res, next) => {
let token = req.header('token') || req.body.token
// Check token
if (!token || !config.tokens[token]) return res.status(402).send('Forbidden')
let v4 = null
let qv4 = req.query.ipv4 || req.body.ipv4
let v6 = null
let qv6 = req.query.ipv6 || req.body.ipv6
// Lets begin our trials
// Determine Address from request headers
if (req.header('http_x_forwarded_for')) {
v4 = req.header('http_x_forwarded_for')
} else if (req.header('remote_addr')) {
v4 = req.header('remote_addr')
} else {
v4 = req.connection.remoteAddress
}
if (!validv4(v4)) {
v6 = v4
v4 = null
}
// IPv4
if (qv4 && validv4(qv4)) {
v4 = qv4
}
if (qv4 === 'ignore') {
v4 = null
}
// IPv6
if (qv6 && validv6(qv6)) {
v6 = qv6
}
if (qv6 === 'ignore') {
v6 = null
}
if (v4 === null && v6 === null) {
res.send('Nothing to do')
}
try {
await updateZone(Object.assign({
token: token,
root: config.named,
}, config.tokens[token]), v4, v6)
} catch (e) {
console.error(e.stack)
return res.status(500).send('Internal Server Error')
}
res.status(204).end()
})
return router
}
module.exports = init

View File

@ -0,0 +1,66 @@
const express = require('express')
const path = require('path')
const fs = require('fs')
const fsp = fs.promises
const router = express.Router()
const cfgLoader = require(path.join('..', '..', 'config-loader'))(path.join(__dirname, 'config.json'), {
root: path.join(process.cwd(), '..', 'fiche', 'code'),
style: 'tomorrow-night'
})
async function init () {
let config = await cfgLoader
let root = path.resolve(config.root)
await fsp.access(root, fs.constants.F_OK)
router.get('/:name', async (req, res, next) => {
let name = req.params.name
if (name.length != 4) {
try {
let text = await fsp.readFile(path.join(root, 'index.html'))
res.send(text)
} catch (e) {
res.status(403)
}
return res.end()
}
let fichePath = path.join(root, name, 'index.txt')
let reqRaw = req.query.raw != null
let text
try {
text = await fsp.readFile(fichePath, {encoding: 'utf8'})
} catch (e) {
return res.status(404).end()
}
if (reqRaw) {
return res.set('Content-Type', 'text/plain').send(text)
}
let etext = text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
let payload =
'<?xml version="1.0" encoding="UTF-8"?>' +
'<html xmlns="http://www.w3.org/1999/xhtml" class="hljs">' +
'<head>' +
'<title>' + name + '</title>' +
'<link rel="stylesheet" href="/assets/css/t.css" />' +
'<link rel="stylesheet" href="/assets/css/t-styles/' + config.style + '.css" />' +
'<script src="/assets/js/highlight.min.js"></script>' +
'</head>' +
'<body>' +
'<pre><code>' + etext + '</code></pre>' +
'<script>hljs.initHighlightingOnLoad();</script>' +
'</body>' +
'</html>'
res.set('Content-Type', 'application/xhtml+xml').send(payload)
})
return router
}
module.exports = init

12
config-loader.js Normal file
View File

@ -0,0 +1,12 @@
const fs = require('fs').promises
module.exports = async function (file, defaults) {
let f
try {
f = await fs.readFile(file)
f = JSON.parse(f)
} catch (e) {
return defaults
}
return Object.assign({}, defaults, f)
}

17
package.json Normal file
View File

@ -0,0 +1,17 @@
{
"name": "serve-lunasqu.ee",
"version": "1.0.0",
"description": "lunasqu.ee - unite segregated services",
"main": "runner.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node runner.js"
},
"private": true,
"license": "Unlicense",
"dependencies": {
"body-parser": "^1.18.3",
"express": "^4.16.4",
"sqlite": "^3.0.1"
}
}

3
runner.js Normal file
View File

@ -0,0 +1,3 @@
const path = require('path')
require(path.join(__dirname, 'server'))().catch((e) => console.error(e))

60
server.js Normal file
View File

@ -0,0 +1,60 @@
const express = require('express')
const bodyParser = require('body-parser')
const path = require('path')
const fs = require('fs')
const fsPromises = fs.promises
const app = express()
let apps = process.env.APPS ? JSON.parse(process.env.APPS) : ['highlight-termbin', 'dyndns', 'distrib-ipfs']
let sock = process.env.SOCKET || '/tmp/serve-lunasqu.ee.t.sock'
app.enable('trust proxy', 1)
app.disable('x-powered-by')
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
function requireNoCache (file) {
let fullPath = path.resolve(file)
let data = require(fullPath)
if (require.cache && require.cache[fullPath]) {
delete require.cache[fullPath]
}
return data
}
async function enableApp (name) {
let papp = path.join(__dirname, 'applications', name)
await fsPromises.access(papp, fs.constants.F_OK)
let runner = requireNoCache(papp)
if (!runner) throw new Error('No such application.')
let router = await runner()
app.use('/' + name, router)
}
async function init () {
for (let i in apps) {
let papp = apps[i]
try {
await enableApp(papp)
console.log(' =>', papp, 'was enabled successfully.')
} catch (e) {
console.error('Failed to start application', papp)
console.error(e.stack)
}
}
try { await fsPromises.unlink(sock) } catch (e) {}
app.listen(sock, function () {
console.log('Started server on', sock)
})
}
module.exports = init