🎉 Sync 2026-04-20 00:26:52

This commit is contained in:
github-actions[bot] 2026-04-20 00:26:52 +08:00
parent 814d40b29a
commit 24fc7da92a
11 changed files with 1152 additions and 0 deletions

View File

@ -0,0 +1,24 @@
#
# Copyright (C) 2026
#
# This is free software, licensed under the Apache License, Version 2.0.
#
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Support for OpenVPN Client
LUCI_DESCRIPTION:=This package provides support for OpenVPN client configuration in LuCI.
LUCI_DEPENDS:=+openvpn +kmod-tun +firewall
LUCI_PKGARCH:=all
PKG_VERSION:=1
PKG_RELEASE:=1
include $(TOPDIR)/feeds/luci/luci.mk
define Package/$(PKG_NAME)/postinst
#!/bin/sh
mkdir -p "$${IPKG_INSTROOT}/etc/openvpn/openvpnc"
exit 0
endef
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,157 @@
'use strict';
'require form';
'require network';
'require uci';
'require ui';
var profileDirectory = '/etc/openvpn/openvpnc';
network.registerPatternVirtual(/^vpn-.+$/);
function getProfilePath(section, section_id) {
return section.formvalue(section_id, 'ovpn_file') ||
uci.get('network', section_id, 'ovpn_file') ||
'%s/default-%s.ovpn'.format(profileDirectory, section_id);
}
function getAuthPath(section, section_id) {
var profile = getProfilePath(section, section_id);
if (profile != null && profile !== '')
return profile.replace(/\.ovpn$/, '.auth');
return '%s/default-%s.auth'.format(profileDirectory, section_id);
}
return network.registerProtocol('openvpnc', {
getI18n: function() {
return _('OpenVPN Client');
},
getIfname: function() {
return this._ubus('l3_device') || 'vpn-%s'.format(this.sid);
},
getOpkgPackage: function() {
return 'openvpn';
},
isFloating: function() {
return true;
},
isVirtual: function() {
return true;
},
getDevices: function() {
return null;
},
containsDevice: function(ifname) {
return (network.getIfnameOf(ifname) == this.getIfname());
},
renderFormOptions: function(s) {
var o;
o = s.taboption('general', form.FileUpload, 'ovpn_file', _('OpenVPN configuration'),
_('Upload or choose the client profile stored under %s. Only .ovpn files are accepted.').format(profileDirectory));
o.root_directory = profileDirectory;
o.enable_remove = false;
o.rmempty = false;
o.renderWidget = function(section_id, option_index, cfgvalue) {
var browserEl = new ui.FileUpload((cfgvalue != null) ? cfgvalue : this.default, {
id: this.cbid(section_id),
name: this.cbid(section_id),
show_hidden: this.show_hidden,
enable_upload: this.enable_upload,
enable_remove: this.enable_remove,
root_directory: this.root_directory,
disabled: (this.readonly != null) ? this.readonly : this.map.readonly
});
browserEl.renderListing = function(container, path, list) {
return ui.FileUpload.prototype.renderListing.apply(this, [
container,
path,
list.filter(function(entry) {
return entry.type == 'directory' ||
(entry.type == 'file' && entry.name.match(/\.ovpn$/));
})
]);
};
return browserEl.render();
};
o.validate = function(section_id, value) {
if (value == null || value === '')
return _('Please upload or choose an .ovpn file');
if (value.indexOf(profileDirectory + '/') !== 0)
return _('Selected file must be stored in %s').format(profileDirectory);
if (!value.match(/\.ovpn$/))
return _('Selected file must use the .ovpn extension');
return true;
};
o = s.taboption('general', form.Value, 'username', _('Username'),
_('If the selected profile uses auth-user-pass, a matching .auth file will be generated automatically from these credentials.'));
o.rmempty = true;
o = s.taboption('general', form.Value, 'password', _('Password'));
o.password = true;
o.rmempty = true;
o = s.taboption('advanced', form.Flag, 'custom_dns_enable', _('Allow custom DNS servers'),
_('When enabled, the DNS servers entered below will be added to this interface even if the OpenVPN server does not push any DNS settings.'));
o.rmempty = false;
o.default = o.disabled;
o = s.taboption('advanced', form.Value, 'custom_dns', _('Custom DNS servers'),
_('Enter one or more DNS server addresses separated by spaces or commas, for example: 1.1.1.1,8.8.8.8'));
o.placeholder = '1.1.1.1 8.8.8.8';
o.depends('custom_dns_enable', '1');
o.rmempty = true;
o = s.taboption('advanced', form.Flag, 'extra_routes_enable', _('Allow extra route networks'),
_('When enabled, the custom route networks entered below will be appended after the route networks pushed by the OpenVPN server.'));
o.rmempty = false;
o.default = o.disabled;
o = s.taboption('advanced', form.Value, 'extra_routes', _('Extra route networks'),
_('Enter one or more IPv4 CIDR networks separated by spaces or commas, for example: 10.0.0.0/24,172.16.10.0/24'));
o.placeholder = '10.0.0.0/24 172.16.10.0/24';
o.depends('extra_routes_enable', '1');
o.rmempty = true;
o = s.taboption('advanced', form.Flag, 'domain_dns_enable', _('Resolve specific domains via custom DNS'),
_('When enabled, dnsmasq will forward the domains entered below to the custom DNS servers through the generated ovpnc.conf file in its active conf-dir directory.'));
o.rmempty = false;
o.default = o.disabled;
o.depends('custom_dns_enable', '1');
o = s.taboption('advanced', form.Value, 'dns_domains', _('Domains resolved by custom DNS'),
_('Enter one or more domains separated by spaces or commas, for example: corp.example.com'));
o.placeholder = 'corp.example.com';
o.depends('domain_dns_enable', '1');
o.rmempty = true;
o = s.taboption('advanced', form.DummyValue, '_auth_file', _('Authentication file'));
o.cfgvalue = function(section_id) {
return getAuthPath(this.section, section_id);
};
o = s.taboption('advanced', form.Value, 'mtu', _('Override MTU'));
o.placeholder = '1500';
o.datatype = 'max(9200)';
o.rmempty = true;
o = s.taboption('advanced', form.DummyValue, '_profile_path', _('Stored profile path'));
o.cfgvalue = function(section_id) {
return getProfilePath(this.section, section_id);
};
}
});

View File

@ -0,0 +1 @@
zh_Hans

View File

@ -0,0 +1,105 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: PACKAGE VERSION\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: zh_Hans\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
#: htdocs/luci-static/resources/protocol/openvpnc.js:22
msgid "OpenVPN Client"
msgstr "OpenVPN 客户端"
#: htdocs/luci-static/resources/protocol/openvpnc.js:49
msgid "OpenVPN configuration"
msgstr "OpenVPN 配置文件"
#: htdocs/luci-static/resources/protocol/openvpnc.js:50
msgid "Upload or choose the client profile stored under %s. Only .ovpn files are accepted."
msgstr "上传或选择保存在 %s 下的客户端配置文件,仅接受 .ovpn 文件。"
#: htdocs/luci-static/resources/protocol/openvpnc.js:80
msgid "Please upload or choose an .ovpn file"
msgstr "请上传或选择一个 .ovpn 文件"
#: htdocs/luci-static/resources/protocol/openvpnc.js:83
msgid "Selected file must be stored in %s"
msgstr "所选文件必须保存在 %s 下"
#: htdocs/luci-static/resources/protocol/openvpnc.js:86
msgid "Selected file must use the .ovpn extension"
msgstr "所选文件必须使用 .ovpn 扩展名"
#: htdocs/luci-static/resources/protocol/openvpnc.js:91
msgid "Username"
msgstr "用户名"
#: htdocs/luci-static/resources/protocol/openvpnc.js:92
msgid "If the selected profile uses auth-user-pass, a matching .auth file will be generated automatically from these credentials."
msgstr "如果所选配置使用 auth-user-pass将根据这里填写的凭据自动生成对应的 .auth 文件。"
#: htdocs/luci-static/resources/protocol/openvpnc.js:96
msgid "Password"
msgstr "密码"
#: htdocs/luci-static/resources/protocol/openvpnc.js:100
msgid "Allow custom DNS servers"
msgstr "启用自定义 DNS 服务器"
#: htdocs/luci-static/resources/protocol/openvpnc.js:101
msgid "When enabled, the DNS servers entered below will be added to this interface even if the OpenVPN server does not push any DNS settings."
msgstr "启用后,即使 OpenVPN 服务端没有下发 DNS 设置,下方填写的 DNS 服务器也会被加入到此接口中。"
#: htdocs/luci-static/resources/protocol/openvpnc.js:106
msgid "Custom DNS servers"
msgstr "自定义 DNS 服务器"
#: htdocs/luci-static/resources/protocol/openvpnc.js:107
msgid "Enter one or more DNS server addresses separated by spaces or commas, for example: 1.1.1.1,8.8.8.8"
msgstr "输入一个或多个 DNS 服务器地址使用空格或逗号分隔例如1.1.1.1,8.8.8.8"
#: htdocs/luci-static/resources/protocol/openvpnc.js:112
msgid "Allow extra route networks"
msgstr "启用附加路由网段"
#: htdocs/luci-static/resources/protocol/openvpnc.js:113
msgid "When enabled, the custom route networks entered below will be appended after the route networks pushed by the OpenVPN server."
msgstr "启用后,下方填写的自定义路由网段会追加到 OpenVPN 服务端下发的路由网段之后。"
#: htdocs/luci-static/resources/protocol/openvpnc.js:118
msgid "Extra route networks"
msgstr "附加路由网段"
#: htdocs/luci-static/resources/protocol/openvpnc.js:119
msgid "Enter one or more IPv4 CIDR networks separated by spaces or commas, for example: 10.0.0.0/24,172.16.10.0/24"
msgstr "输入一个或多个 IPv4 CIDR 网段使用空格或逗号分隔例如10.0.0.0/24,172.16.10.0/24"
#: htdocs/luci-static/resources/protocol/openvpnc.js:124
msgid "Resolve specific domains via custom DNS"
msgstr "使用自定义 DNS 解析特定域名"
#: htdocs/luci-static/resources/protocol/openvpnc.js:125
msgid "When enabled, dnsmasq will forward the domains entered below to the custom DNS servers through the generated ovpnc.conf file in its active conf-dir directory."
msgstr "启用后dnsmasq 会在当前生效的 conf-dir 目录中生成 ovpnc.conf并将下方填写的域名转发到自定义 DNS 服务器。"
#: htdocs/luci-static/resources/protocol/openvpnc.js:131
msgid "Domains resolved by custom DNS"
msgstr "由自定义 DNS 解析的域名"
#: htdocs/luci-static/resources/protocol/openvpnc.js:132
msgid "Enter one or more domains separated by spaces or commas, for example: corp.example.com"
msgstr "输入一个或多个域名使用空格或逗号分隔例如corp.example.com"
#: htdocs/luci-static/resources/protocol/openvpnc.js:137
msgid "Authentication file"
msgstr "认证文件路径"
#: htdocs/luci-static/resources/protocol/openvpnc.js:142
msgid "Override MTU"
msgstr "自定义 MTU"
#: htdocs/luci-static/resources/protocol/openvpnc.js:147
msgid "Stored profile path"
msgstr "配置文件保存路径"

View File

@ -0,0 +1,86 @@
#!/bin/sh
[ "$ACTION" = "ifup" ] || exit 0
[ "$INTERFACE" = "wan" ] || exit 0
[ -x /sbin/ifup ] || exit 0
[ -x /sbin/ifdown ] || exit 0
. /lib/functions.sh
openvpnc_find_stale_pids() {
local section="$1"
local runtime_profile="/var/etc/openvpnc-$section.ovpn"
local status_file="/var/run/openvpnc-$section.status"
local proc pid cmdline
for proc in /proc/[0-9]*; do
[ -r "$proc/cmdline" ] || continue
pid="${proc##*/}"
cmdline="$(tr '\000' ' ' < "$proc/cmdline" 2>/dev/null)"
case "$cmdline" in
*"/usr/sbin/openvpn "*"$runtime_profile"*|*"/usr/sbin/openvpn "*"$status_file"*)
echo "$pid"
;;
esac
done
}
openvpnc_kill_stale_processes() {
local section="$1"
local pid pids=""
for pid in $(openvpnc_find_stale_pids "$section"); do
pids="$pids $pid"
done
pids="${pids# }"
[ -n "$pids" ] || return 0
logger -t openvpnc-hotplug "terminating stale openvpnc process(es) for $section: $pids"
kill $pids >/dev/null 2>&1
sleep 2
for pid in $pids; do
[ -d "/proc/$pid" ] || continue
kill -9 "$pid" >/dev/null 2>&1
done
}
openvpnc_restart_interface() {
local section="$1"
local proto auto
config_get proto "$section" proto
[ "$proto" = "openvpnc" ] || return 0
config_get auto "$section" auto 1
[ "$auto" = "0" ] && return 0
logger -t openvpnc-hotplug "restarting interface $section after wan ifup"
/sbin/ifdown "$section" >/dev/null 2>&1
openvpnc_kill_stale_processes "$section"
/sbin/ifup "$section" >/dev/null 2>&1
}
LOCK_FILE="/var/lock/openvpnc-wan-ifup.lock"
mkdir -p "${LOCK_FILE%/*}"
if [ -s "$LOCK_FILE" ]; then
read -r pid < "$LOCK_FILE"
if [ -n "$pid" ] && [ -d "/proc/$pid" ]; then
exit 0
fi
rm -f "$LOCK_FILE"
fi
(
trap 'rm -f "$LOCK_FILE"' EXIT
echo $$ > "$LOCK_FILE"
logger -t openvpnc-hotplug "scheduled openvpnc restart 10 seconds after wan ifup"
sleep 10
config_load network
config_foreach openvpnc_restart_interface interface
) >/dev/null 2>&1 &

View File

@ -0,0 +1,9 @@
#!/bin/sh
. /lib/netifd/netifd-proto.sh
[ -n "$OPENVPNC_ID" ] || exit 0
[ -n "$dev" ] || exit 0
proto_init_update "$dev" 0
proto_send_update "$OPENVPNC_ID"

View File

@ -0,0 +1,189 @@
#!/bin/sh
. /lib/netifd/netifd-proto.sh
[ -n "$OPENVPNC_ID" ] || exit 0
[ -n "$dev" ] || exit 0
openvpnc_prefix() {
case "$1" in
255.255.255.255) echo 32 ;;
255.255.255.254) echo 31 ;;
255.255.255.252) echo 30 ;;
255.255.255.248) echo 29 ;;
255.255.255.240) echo 28 ;;
255.255.255.224) echo 27 ;;
255.255.255.192) echo 26 ;;
255.255.255.128) echo 25 ;;
255.255.255.0) echo 24 ;;
255.255.254.0) echo 23 ;;
255.255.252.0) echo 22 ;;
255.255.248.0) echo 21 ;;
255.255.240.0) echo 20 ;;
255.255.224.0) echo 19 ;;
255.255.192.0) echo 18 ;;
255.255.128.0) echo 17 ;;
255.255.0.0) echo 16 ;;
255.254.0.0) echo 15 ;;
255.252.0.0) echo 14 ;;
255.248.0.0) echo 13 ;;
255.240.0.0) echo 12 ;;
255.224.0.0) echo 11 ;;
255.192.0.0) echo 10 ;;
255.128.0.0) echo 9 ;;
255.0.0.0) echo 8 ;;
254.0.0.0) echo 7 ;;
252.0.0.0) echo 6 ;;
248.0.0.0) echo 5 ;;
240.0.0.0) echo 4 ;;
224.0.0.0) echo 3 ;;
192.0.0.0) echo 2 ;;
128.0.0.0) echo 1 ;;
0.0.0.0) echo 0 ;;
*) echo "" ;;
esac
}
openvpnc_getvar() {
local name="$1"
eval "printf '%s' \"\${$name}\""
}
openvpnc_ucivalue() {
uci -q get "network.${OPENVPNC_ID}.$1"
}
openvpnc_enabled() {
case "$1" in
1|on|true|yes|enabled) return 0 ;;
*) return 1 ;;
esac
}
openvpnc_words() {
printf '%s' "$1" | tr ',\n\t' ' '
}
openvpnc_cidr_split() {
local cidr="$1"
local address prefix
case "$cidr" in
*/*)
address="${cidr%/*}"
prefix="${cidr#*/}"
case "$prefix" in
''|*[!0-9]*) return 1 ;;
*) ;;
esac
[ "$prefix" -ge 0 ] && [ "$prefix" -le 32 ] || return 1
printf '%s %s\n' "$address" "$prefix"
return 0
;;
*)
return 1
;;
esac
}
openvpnc_add_dns_data() {
local index=1
local option
local value
local rest
while :; do
option="$(openvpnc_getvar "foreign_option_$index")"
[ -n "$option" ] || break
case "$option" in
"dhcp-option DNS "*)
value="${option#dhcp-option DNS }"
[ -n "$value" ] && proto_add_dns_server "$value"
;;
"dhcp-option DOMAIN "*)
value="${option#dhcp-option DOMAIN }"
[ -n "$value" ] && proto_add_dns_search "$value"
;;
"dhcp-option DOMAIN-SEARCH "*)
rest="${option#dhcp-option DOMAIN-SEARCH }"
for value in $rest; do
[ -n "$value" ] && proto_add_dns_search "$value"
done
;;
esac
index=$((index + 1))
done
}
openvpnc_add_custom_dns_data() {
local enabled dns_list dns
enabled="$(openvpnc_ucivalue custom_dns_enable)"
openvpnc_enabled "$enabled" || return 0
dns_list="$(openvpnc_ucivalue custom_dns)"
for dns in $(openvpnc_words "$dns_list"); do
[ -n "$dns" ] && proto_add_dns_server "$dns"
done
}
openvpnc_add_route_data() {
local source_ip="$1"
local fallback_gw="$2"
local index=1
local network
local netmask
local gateway
local prefix
while :; do
network="$(openvpnc_getvar "route_network_$index")"
[ -n "$network" ] || break
netmask="$(openvpnc_getvar "route_netmask_$index")"
gateway="$(openvpnc_getvar "route_gateway_$index")"
[ -n "$gateway" ] || gateway="$fallback_gw"
prefix="$(openvpnc_prefix "$netmask")"
[ -n "$prefix" ] || prefix=32
proto_add_ipv4_route "$network" "$prefix" "$gateway" "$source_ip"
index=$((index + 1))
done
}
openvpnc_add_custom_route_data() {
local source_ip="$1"
local fallback_gw="$2"
local enabled route_list route_entry parsed network prefix
enabled="$(openvpnc_ucivalue extra_routes_enable)"
openvpnc_enabled "$enabled" || return 0
route_list="$(openvpnc_ucivalue extra_routes)"
for route_entry in $(openvpnc_words "$route_list"); do
parsed="$(openvpnc_cidr_split "$route_entry")" || continue
network="${parsed% *}"
prefix="${parsed#* }"
[ -n "$network" ] && [ -n "$prefix" ] && proto_add_ipv4_route "$network" "$prefix" "$fallback_gw" "$source_ip"
done
}
local_ip="${ifconfig_local:-$4}"
remote_ip="${ifconfig_remote:-$5}"
prefix="$(openvpnc_prefix "$ifconfig_netmask")"
[ -n "$local_ip" ] || local_ip="$route_vpn_gateway"
[ -n "$prefix" ] || [ -n "$remote_ip" ] && prefix=30
[ -n "$prefix" ] || prefix=32
proto_init_update "$dev" 1 1
proto_set_keep 1
[ -n "$local_ip" ] && proto_add_ipv4_address "$local_ip" "$prefix" "" "$remote_ip"
openvpnc_add_dns_data
openvpnc_add_custom_dns_data
openvpnc_add_route_data "$local_ip" "$route_vpn_gateway"
openvpnc_add_custom_route_data "$local_ip" "$route_vpn_gateway"
proto_send_update "$OPENVPNC_ID"

View File

@ -0,0 +1,442 @@
#!/bin/sh
[ -n "$INCLUDE_ONLY" ] || {
. /lib/functions.sh
. ../netifd-proto.sh
init_proto "$@"
}
openvpnc_get_first_value() {
local file="$1"
local key="$2"
awk -v key="$key" '
/^[[:space:]]*[#;]/ { next }
$1 == key {
print $2
exit
}
' "$file"
}
openvpnc_is_ipv4() {
local value="$1"
local octet
case "$value" in
''|*[!0-9.]*|*.*.*.*.*)
return 1
;;
esac
local old_ifs="$IFS"
IFS='.'
set -- $value
IFS="$old_ifs"
[ "$#" -eq 4 ] || return 1
for octet in "$@"; do
[ -n "$octet" ] || return 1
[ "$octet" -ge 0 ] 2>/dev/null || return 1
[ "$octet" -le 255 ] || return 1
done
return 0
}
openvpnc_get_upstream_dns_servers() {
local resolv_file
for resolv_file in /tmp/resolv.conf.d/resolv.conf.auto /tmp/resolv.conf.auto; do
[ -r "$resolv_file" ] || continue
awk '/^nameserver / { print $2 }' "$resolv_file"
done | awk '!seen[$0]++'
}
openvpnc_resolve_host() {
local host="$1"
local ip
local server
openvpnc_is_ipv4 "$host" && {
echo "$host"
return 0
}
if command -v nslookup >/dev/null 2>&1; then
for server in $(openvpnc_get_upstream_dns_servers); do
openvpnc_is_ipv4 "$server" || continue
ip="$({ nslookup "$host" "$server" 2>/dev/null || true; } | awk '/^Address [0-9]+: / { print $3; exit }')"
openvpnc_is_ipv4 "$ip" || continue
echo "$ip"
return 0
done
fi
for ip in $(resolveip -t 5 "$host" 2>/dev/null); do
openvpnc_is_ipv4 "$ip" || continue
echo "$ip"
return 0
done
return 1
}
openvpnc_get_dev_type() {
local file="$1"
local dev_type
local dev
dev_type="$(openvpnc_get_first_value "$file" "dev-type")"
[ -n "$dev_type" ] && {
echo "$dev_type"
return
}
dev="$(openvpnc_get_first_value "$file" "dev")"
case "$dev" in
tap*) echo "tap" ;;
*) echo "tun" ;;
esac
}
openvpnc_get_auth_file() {
local file="$1"
local mode
local sidecar
mode="$(awk '
/^[[:space:]]*[#;]/ { next }
$1 == "auth-user-pass" {
if (NF >= 2) {
print "configured"
} else {
print "required"
}
exit
}
' "$file")"
case "$mode" in
"")
return 1
;;
configured)
return 2
;;
required)
sidecar="${file%.ovpn}.auth"
[ -f "$sidecar" ] || return 3
echo "$sidecar"
return 0
;;
esac
return 1
}
openvpnc_add_host_dependencies() {
local config="$1"
local file="$2"
local host
local ip
for host in $(awk '
/^[[:space:]]*[#;]/ { next }
$1 == "remote" && NF >= 2 { print $2 }
' "$file" | awk '!seen[$0]++'); do
[ -n "$host" ] || continue
ip="$(openvpnc_resolve_host "$host")" || continue
proto_add_host_dependency "$config" "$ip"
done
}
openvpnc_prepare_profile() {
local config="$1"
local file="$2"
local tmp_file="/var/etc/openvpnc-$config.ovpn"
local host
local ip
local rewrites=''
local changed=0
for host in $(awk '
/^[[:space:]]*[#;]/ { next }
$1 == "remote" && NF >= 2 { print $2 }
' "$file" | awk '!seen[$0]++'); do
openvpnc_is_ipv4 "$host" && continue
ip="$(openvpnc_resolve_host "$host")" || continue
rewrites="${rewrites}${host}=${ip}|"
changed=1
logger -t openvpnc "resolved remote host $host to $ip for $config"
done
if [ "$changed" != "1" ]; then
echo "$file"
return 0
fi
awk -v rewrites="$rewrites" '
BEGIN {
n = split(rewrites, items, /\|/)
for (i = 1; i <= n; i++) {
if (items[i] == "")
continue
split(items[i], pair, /=/)
map[pair[1]] = pair[2]
}
}
/^[[:space:]]*[#;]/ {
print
next
}
$1 == "remote" && NF >= 2 && ($2 in map) {
$2 = map[$2]
}
{
print
}
' "$file" > "$tmp_file" || return 1
echo "$tmp_file"
}
openvpnc_write_auth_file() {
local file="$1"
local username="$2"
local password="$3"
mkdir -p "${file%/*}" || return 1
umask 077
{
printf '%s\n' "$username"
printf '%s\n' "$password"
} > "$file"
}
openvpnc_zone_name() {
printf 'ovpnc'
}
openvpnc_zone_section() {
openvpnc_zone_name "$1"
}
openvpnc_forwarding_section() {
printf '%s_fwd' "$(openvpnc_zone_name "$1")"
}
openvpnc_firewall_reload() {
/etc/init.d/firewall enabled >/dev/null 2>&1 || return 0
/etc/init.d/firewall reload >/dev/null 2>&1
}
openvpnc_sync_dnsmasq() {
[ -x /usr/bin/openvpnc-dnsmasq-sync ] || return 0
/usr/bin/openvpnc-dnsmasq-sync >/dev/null 2>&1
}
openvpnc_ensure_firewall() {
local config="$1"
local zone_section forwarding_section zone_name changed=0
local legacy_zone_section legacy_forwarding_section
zone_section="$(openvpnc_zone_section "$config")"
forwarding_section="$(openvpnc_forwarding_section "$config")"
zone_name="$zone_section"
legacy_zone_section="$(printf 'openvpnc_%s' "$config" | tr -c 'A-Za-z0-9_' '_')"
legacy_forwarding_section="$(printf 'openvpnc_%s_lan_fwd' "$config" | tr -c 'A-Za-z0-9_' '_')"
for old_section in "$legacy_zone_section" "$legacy_forwarding_section" ovpn_ ovpn__fwd ovpnc_lan_fwd_; do
if uci -q get firewall.$old_section >/dev/null; then
uci -q delete firewall.$old_section
changed=1
fi
done
if [ "$(uci -q get firewall.$zone_section)" != "zone" ]; then
uci -q set firewall.$zone_section=zone
changed=1
fi
if [ "$(uci -q get firewall.$zone_section.name)" != "$zone_name" ]; then
uci -q set firewall.$zone_section.name="$zone_name"
changed=1
fi
if [ "$(uci -q get firewall.$zone_section.input)" != "REJECT" ]; then
uci -q set firewall.$zone_section.input='REJECT'
changed=1
fi
if [ "$(uci -q get firewall.$zone_section.output)" != "ACCEPT" ]; then
uci -q set firewall.$zone_section.output='ACCEPT'
changed=1
fi
if [ "$(uci -q get firewall.$zone_section.forward)" != "REJECT" ]; then
uci -q set firewall.$zone_section.forward='REJECT'
changed=1
fi
if [ "$(uci -q get firewall.$zone_section.masq)" != "1" ]; then
uci -q set firewall.$zone_section.masq='1'
changed=1
fi
if [ "$(uci -q get firewall.$zone_section.mtu_fix)" != "1" ]; then
uci -q set firewall.$zone_section.mtu_fix='1'
changed=1
fi
if [ "$(uci -q get firewall.$zone_section.family)" != "ipv4" ]; then
uci -q set firewall.$zone_section.family='ipv4'
changed=1
fi
if [ "$(uci -q get firewall.$zone_section.network)" != "$config" ]; then
uci -q delete firewall.$zone_section.network
uci add_list firewall.$zone_section.network="$config"
changed=1
fi
if [ "$(uci -q get firewall.$forwarding_section)" != "forwarding" ]; then
uci -q set firewall.$forwarding_section=forwarding
changed=1
fi
if [ "$(uci -q get firewall.$forwarding_section.src)" != "lan" ]; then
uci -q set firewall.$forwarding_section.src='lan'
changed=1
fi
if [ "$(uci -q get firewall.$forwarding_section.dest)" != "$zone_name" ]; then
uci -q set firewall.$forwarding_section.dest="$zone_name"
changed=1
fi
if [ "$changed" = "1" ]; then
uci commit firewall
logger -t openvpnc "updated firewall zone $zone_name for interface $config"
openvpnc_firewall_reload
fi
}
proto_openvpnc_init_config() {
proto_config_add_string "ovpn_file"
proto_config_add_string "username"
proto_config_add_string "password"
proto_config_add_int "mtu"
available=1
no_device=1
}
proto_openvpnc_setup() {
local config="$1"
local ovpn_file username password mtu
local ifname dev_type auth_file auth_state auth_required auth_inline ovpn_run_file
json_get_vars ovpn_file username password mtu
[ -n "$username" ] || username="$(uci -q get network.$config.username)"
[ -n "$password" ] || password="$(uci -q get network.$config.password)"
ifname="vpn-$config"
mkdir -p /etc/openvpn/openvpnc
[ -n "$ovpn_file" ] && [ -f "$ovpn_file" ] || {
logger -t openvpnc "missing profile for $config"
proto_setup_failed "$config"
proto_block_restart "$config"
return 1
}
openvpnc_add_host_dependencies "$config" "$ovpn_file"
mkdir -p /var/run /var/etc
ovpn_run_file="$(openvpnc_prepare_profile "$config" "$ovpn_file")" || {
logger -t openvpnc "failed to prepare runtime profile for $config"
proto_setup_failed "$config"
proto_block_restart "$config"
return 1
}
dev_type="$(openvpnc_get_dev_type "$ovpn_file")"
auth_file="$(openvpnc_get_auth_file "$ovpn_file")"
auth_state="$?"
auth_required=0
auth_inline=0
case "$auth_state" in
0|3)
auth_required=1
auth_file="${ovpn_file%.ovpn}.auth"
;;
2)
auth_required=1
auth_inline=1
;;
esac
if [ -n "$username$password" ]; then
[ -n "$username" ] && [ -n "$password" ] || {
logger -t openvpnc "username and password must both be set for $config"
proto_setup_failed "$config"
proto_block_restart "$config"
return 1
}
auth_file="${ovpn_file%.ovpn}.auth"
openvpnc_write_auth_file "$auth_file" "$username" "$password" || {
logger -t openvpnc "failed to write auth file $auth_file for $config"
proto_setup_failed "$config"
proto_block_restart "$config"
return 1
}
auth_required=1
auth_state=0
fi
if [ "$auth_state" = "3" ]; then
logger -t openvpnc "profile $ovpn_file requires auth-user-pass; set username and password in UCI or create ${ovpn_file%.ovpn}.auth with username on first line and password on second line"
proto_setup_failed "$config"
proto_block_restart "$config"
return 1
fi
openvpnc_ensure_firewall "$config"
openvpnc_sync_dnsmasq
set -- /usr/sbin/openvpn \
--syslog "openvpnc($config)" \
--status "/var/run/openvpnc-$config.status" \
--cd "${ovpn_file%/*}" \
--config "$ovpn_run_file" \
--dev "$ifname" \
--dev-type "$dev_type" \
--script-security 2 \
--up /lib/netifd/openvpnc-up \
--down-pre \
--down /lib/netifd/openvpnc-down \
--setenv OPENVPNC_ID "$config"
[ -n "$mtu" ] && set -- "$@" --tun-mtu "$mtu"
if [ "$auth_required" = "1" ] && [ "$auth_inline" = "0" ]; then
set -- "$@" --auth-user-pass "$auth_file"
fi
proto_run_command "$config" "$@"
}
proto_openvpnc_teardown() {
local config="$1"
rm -f "/var/run/openvpnc-$config.status"
rm -f "/var/etc/openvpnc-$config.ovpn"
openvpnc_sync_dnsmasq
proto_kill_command "$config"
}
[ -n "$INCLUDE_ONLY" ] || {
add_protocol openvpnc
}

View File

@ -0,0 +1 @@
/etc/openvpn/openvpnc/

View File

@ -0,0 +1,116 @@
#!/bin/sh
. /lib/functions.sh
OPENVPNC_DNSMASQ_DIRS=
openvpnc_append_dnsmasq_dir() {
local section="$1"
local dnsmasq_conf="/tmp/etc/dnsmasq.conf.$section"
local dnsmasq_dir
[ -f "$dnsmasq_conf" ] || return 0
dnsmasq_dir="$(awk -F '=' '/^conf-dir=/ {print $2; exit}' "$dnsmasq_conf")"
dnsmasq_dir="${dnsmasq_dir%%,*}"
dnsmasq_dir="${dnsmasq_dir%/}"
[ -n "$dnsmasq_dir" ] || return 0
case " $OPENVPNC_DNSMASQ_DIRS " in
*" $dnsmasq_dir "*) return 0 ;;
esac
append OPENVPNC_DNSMASQ_DIRS "$dnsmasq_dir"
}
openvpnc_get_dnsmasq_dirs() {
local default_dir="/tmp/dnsmasq.d"
if [ -f /etc/openwrt_release ]; then
OPENVPNC_DNSMASQ_DIRS=
config_load dhcp
config_foreach openvpnc_append_dnsmasq_dir dnsmasq
fi
[ -n "$OPENVPNC_DNSMASQ_DIRS" ] || OPENVPNC_DNSMASQ_DIRS="$default_dir"
printf '%s\n' $OPENVPNC_DNSMASQ_DIRS
}
openvpnc_prepare_dnsmasq_file() {
OPENVPNC_DNSMASQ_DIR="$1"
OPENVPNC_DNSMASQ_FILE="$OPENVPNC_DNSMASQ_DIR/ovpnc.conf"
OPENVPNC_DNSMASQ_TMP="$OPENVPNC_DNSMASQ_FILE.tmp"
mkdir -p "$OPENVPNC_DNSMASQ_DIR" || return 1
: > "$OPENVPNC_DNSMASQ_TMP" || return 1
config_foreach openvpnc_append_dnsmasq_section interface
if [ -s "$OPENVPNC_DNSMASQ_TMP" ]; then
mv "$OPENVPNC_DNSMASQ_TMP" "$OPENVPNC_DNSMASQ_FILE"
else
rm -f "$OPENVPNC_DNSMASQ_TMP" "$OPENVPNC_DNSMASQ_FILE"
fi
}
openvpnc_enabled() {
case "$1" in
1|on|true|yes|enabled) return 0 ;;
*) return 1 ;;
esac
}
openvpnc_words() {
printf '%s' "$1" | tr ',\n\t' ' '
}
openvpnc_clean_domain() {
local domain="$1"
domain="${domain#/}"
domain="${domain#/}"
domain="${domain#.}"
domain="${domain%.}"
domain="${domain%/}"
printf '%s' "$domain"
}
openvpnc_append_dnsmasq_section() {
local section="$1"
local proto enabled dns_servers domains dns_server domain
config_get proto "$section" proto
[ "$proto" = "openvpnc" ] || return 0
config_get enabled "$section" custom_dns_enable
openvpnc_enabled "$enabled" || return 0
config_get dns_servers "$section" custom_dns
[ -n "$dns_servers" ] || return 0
config_get enabled "$section" domain_dns_enable
openvpnc_enabled "$enabled" || return 0
config_get domains "$section" dns_domains
[ -n "$domains" ] || return 0
for domain in $(openvpnc_words "$domains"); do
domain="$(openvpnc_clean_domain "$domain")"
[ -n "$domain" ] || continue
echo "rebind-domain-ok=/$domain/" >> "$OPENVPNC_DNSMASQ_TMP"
for dns_server in $(openvpnc_words "$dns_servers"); do
[ -n "$dns_server" ] || continue
echo "server=/$domain/$dns_server" >> "$OPENVPNC_DNSMASQ_TMP"
done
done
}
config_load network
for OPENVPNC_DNSMASQ_DIR in $(openvpnc_get_dnsmasq_dirs); do
openvpnc_prepare_dnsmasq_file "$OPENVPNC_DNSMASQ_DIR"
done
if /etc/init.d/dnsmasq enabled >/dev/null 2>&1; then
/etc/init.d/dnsmasq restart >/dev/null 2>&1
fi

View File

@ -0,0 +1,22 @@
{
"luci-proto-openvpnc": {
"description": "Grant access to LuCI OpenVPN Client procedures",
"read": {
"file": {
"/etc/openvpn/openvpnc": [ "list" ],
"/etc/openvpn/openvpnc/*.ovpn": [ "read" ]
},
"ubus": {
"file": [ "list", "stat" ]
},
"uci": [ "network" ]
},
"write": {
"cgi-io": [ "upload" ],
"file": {
"/etc/openvpn/openvpnc/*.ovpn": [ "write" ]
},
"uci": [ "network" ]
}
}
}