summaryrefslogtreecommitdiffstats
path: root/protocols/luci-proto-openvpn/root/usr/share/rpcd/ucode/luci.openvpn.uc
blob: eb3b418f855d24ae0a34d5d09a289b9342098129 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#!/usr/bin/env ucode

'use strict';

import { stdin, access, chmod, dirname, basename, open, popen, glob, lsdir, readfile, readlink, error, mkdir } from 'fs';
import { cursor } from 'uci';
import { connect } from 'ubus';


const openvpn_dir = '/etc/openvpn';

function shellquote(s) {
	return `'${replace(s, "'", "'\\''")}'`;
}

function command(cmd) {
	return trim(popen(cmd)?.read?.('all'));
}

function makedirs(path) {
	const parts = split(path, '/');
	const grow = [];
	for(let i = 0; i < length(parts); i++) {
		push(grow, parts[i]);
		mkdir(join('/', grow));
	}
	return join('/', grow);
}

function keyDir(ifname, kt) {
	return `${openvpn_dir}/${ifname}/${kt}`;
}

const methods = {
	generateKey: {
		args: { keytype: 'keytype', ifname: 'ifname', server_key: 'server_key', cl_meta: 'c_metadata' },
		call: function(req) {
			const kt = req.args?.keytype;
			const ifname = req.args?.ifname || 'unnamed';
			const ts = time();

			if (!kt) return { error: 'missing keytype' };

			let dir;
			let outfile = `${ifname}_${kt}_${ts}.key`;

			let mkpath = makedirs(`${keyDir(ifname, kt)}`);
			let path = `${mkpath}/${outfile}`;

			// find openvpn binary
			const openvpn = trim(popen('command -v openvpn 2>/dev/null')?.read?.('all'));
			if (!length(openvpn)) return { error: 'openvpn binary not found' };

			let cmd;
			if (kt == 'tls-crypt-v2-client') {
				// client generation needs server key
				let server_key = req.args?.server_key;
				if (!server_key) {
					// try to pick latest server key for same interface
					const serverDir = `${keyDir(ifname, 'tls-crypt-v2-server')}`;
					const list = lsdir(serverDir);
					if (length(list) > 0) server_key = serverDir + '/' + list[-1];
				} else {
					server_key = `${keyDir(ifname, 'tls-crypt-v2-server')}/${req.args?.server_key}`;
				}

				if (!server_key) return { error: 'missing server_key for tls-crypt-v2-client' };

				// denote which server key this client key is derived from in the name
				path = `${mkpath}/${ifname}_${kt}_${ts}-${req.args?.server_key}`;
				cmd = `${openvpn} --tls-crypt-v2 ${shellquote(server_key)} --genkey tls-crypt-v2-client ${shellquote(path)} ${shellquote(req.args?.cl_meta)} 2>/dev/null`;
			} else {
				// basic genkey
				cmd = `${openvpn} --genkey ${kt} ${shellquote(path)} 2>/dev/null`;
			}

			const out = popen(cmd)?.read?.('all') || '';

			// ensure permissions
			chmod(path, 0o600);

			let content = '';
			try { content = readfile(path); } catch (e) { /* ignore */ }

			if (!length(content)) {
				return { error: 'failed to generate key', cmd_output: out };
			}

			return { path, filename: outfile, content };
		}
	},

	getSKeys: {
		args: { ifname: 'ifname' },
		call: function(req) {
			const serverDir = `${keyDir(req.args?.ifname, 'tls-crypt-v2-server')}`;
			const list = lsdir(serverDir);

			return { skeys: list, path: serverDir };
		}
	}
};

return { 'luci.openvpn': methods };