#!/bin/bash #----------------------------------------------------------------------------------------------------------------------------------- # # Rx3 VPN Admin Board CGI # # Copyright (C) 2025-2026 Arnaud G. GIBERT # mailto:arnaud@rx3.net # # This is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this program; If not, see # . # #----------------------------------------------------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------------------------------------------------- # Includes #----------------------------------------------------------------------------------------------------------------------------------- : "${RX3_LIB_DIR:=/usr/lib/rx3}" . "${RX3_LIB_DIR}/network.bash" #----------------------------------------------------------------------------------------------------------------------------------- # Global Variables #----------------------------------------------------------------------------------------------------------------------------------- declare -g VERSION="1.0.0" declare -g NAME="vpn-admin_board.cgi" declare -g DEBUG="" #declare -g DEBUG="echo" #declare -g DEBUG=":" # No Log please export LOG="" declare -g TIME_IN=$(date +%s%N) declare -g CMD="" declare -g FORMAT="" declare -g USER="" declare -g STATUS="" declare -g IP="" declare -g VPN="" declare -g TYPE="" declare -g REDIRECT="" declare -g CMD_STATUS="" declare -g ADMIN="" declare -g FILTER="" declare -g DEFROUTE="" declare -g FILENAME="" declare -g FILE_NAME="" declare -g HOST_NAME="" declare -g ADMIN_MODE="" declare -g USER_MODE="" declare -g STATUS_MODE="" #----------------------------------------------------------------------------------------------------------------------------------- # Header Print #----------------------------------------------------------------------------------------------------------------------------------- vab_header_print() { case "${FORMAT}" in "html") echo "Content-type: text/html" echo "" echo "" echo "" echo " " echo " " echo " " echo " " echo " " echo " " if [[ "${CMD_STATUS}" == "" ]] then echo " Rx3 VPN Admin Board" else echo " Rx3 VPN Admin Board: ${CMD_STATUS}" fi if [[ "${REDIRECT}" != "" ]] then echo " " fi echo " " echo " " ;; "csv") echo "Content-type: text/csv" echo "" echo "SOF" if [[ "${REDIRECT}" != "" ]] then echo "CMD: ${CMD_STATUS}" fi ;; "txt") echo "Content-disposition: attachment; filename=${FILE_NAME}" echo "Content-type: text/plain" echo "" ;; esac } #----------------------------------------------------------------------------------------------------------------------------------- # Footer Print #----------------------------------------------------------------------------------------------------------------------------------- vab_footer_print() { case "${FORMAT}" in "html") echo " " echo "" echo "" ;; "csv") echo "" echo "EOF" ;; esac } #----------------------------------------------------------------------------------------------------------------------------------- # Destination Status Board #----------------------------------------------------------------------------------------------------------------------------------- vab_destination_status_board() { local dst_id if [[ "${FORMAT}" == "html" ]] then echo "

" echo "
" echo "

" echo "" echo "

Destination Status Board

" echo "" echo " " echo " " else echo "TABLE: Destination_Status_Board" echo "#;Name;Type;Device;Status;IP;Host Name;Config;Table;Bytes In;Bytes Out;UpTime" fi for dst_id in ${NETWORK_DST_ID_LIST} do network_dst_tab_get "${dst_id}" if [[ "${dst_status}" == "0" ]] then dst_ip="-" fi if [[ ( "${STATUS}" == "" ) || ( "${STATUS}" == "${dst_status}") ]] then if [[ "${FORMAT}" == "html" ]] then echo -n " " case "${dst_status}" in "0") echo -n "" ;; "1") echo -n "" ;; "2") echo -n "" ;; *) echo -n "" ;; esac echo "" else echo "${dst_id};${dst_name};${NETWORK_DST_TYPE[${dst_type}]};${dst_device};${dst_status};${dst_ip};${dst_host_name};${dst_config};${dst_table};${dst_bytes_received};${dst_bytes_sent};${dst_uptime}" fi fi done if [[ "${FORMAT}" == "html" ]] then echo "
#NameTypeDeviceStatusIPHost NameConfigTableBytes InBytes OutUpTime
${dst_id}${dst_name}${NETWORK_DST_TYPE[${dst_type}]}${dst_device}\"Down\"\"Up\"\"Unready\"\"Unknown\"${dst_ip:--}${dst_host_name:--}${dst_config:--}${dst_table}${dst_bytes_received:--}${dst_bytes_sent:--}${dst_uptime:--}
" echo "

" echo "
" echo "

" echo "" else echo "" fi } #----------------------------------------------------------------------------------------------------------------------------------- # Source Routing Board Line #----------------------------------------------------------------------------------------------------------------------------------- vab_source_routing_board_line() { local src_id="$1" local class local dst_id network_src_tab_get "${src_id}" if [[ ( "${USER}" == "" ) || ( "${USER}" == "owner") || ( "${USER}" == "${src_owner}") ]] then if [[ ( "${ADMIN}" == "true") || ( "${REMOTE_USER}" == "${src_owner}") ]] then class="default" else if [[ "${USER}" == "owner" ]] then class="skip" else class="dark" fi fi else class="skip" fi if [[ ( "${STATUS}" != "" ) && ( "${STATUS}" != "${src_status}") ]] then class="skip" fi if [[ "${class}" != "skip" ]] then if [[ "${FORMAT}" == "html" ]] then echo -n " ${src_id}" echo -n "${NETWORK_SRC_TYPE[${src_type}]}${src_ip}${src_host_name:--}" echo -n "${src_device:--}" case "${src_status}" in "0") echo -n "\"Down\"" ;; "1") echo -n "\"Up\"" ;; "2") echo -n "-" ;; esac else echo -n "${src_id};${NETWORK_SRC_TYPE[${src_type}]};${src_ip};${src_host_name};${src_device};${src_status};" fi for dst_id in ${NETWORK_DST_ID_LIST} do network_dst_tab_get "${dst_id}" if [[ ( "${ADMIN}" == "true") || ( "${dst_type}" != "1") ]] then if [[ "${FORMAT}" == "html" ]] then echo -n "
" if [[ "${dst_table}" == "${src_table}" ]] then echo -n "\"Up\" " else echo -n "\"Down\"" if [[ ( "${ADMIN}" == "true") || ( "${REMOTE_USER}" == "${src_owner}") ]] then echo -n "\"Activate\"" else echo -n " " fi fi echo -n "
" else if [[ "${dst_table}" == "${src_table}" ]] then echo -n "1;" else echo -n "0;" fi fi fi done if [[ "${FORMAT}" == "html" ]] then if [[ "${src_port_range}" != "0" ]] then echo -n "${src_port_range}${src_port_start:--}${src_port_end:--}" else echo -n "${src_port_range}--" fi echo "${src_owner}${src_bytes_received:--}${src_bytes_sent:--}${src_uptime:--}${src_last_seen:--}" else echo "${src_port_range};${src_port_start};${src_port_end};${src_owner};${src_bytes_received};${src_bytes_sent};${src_uptime};${src_last_seen}" fi fi } #----------------------------------------------------------------------------------------------------------------------------------- # Source Routing Board #----------------------------------------------------------------------------------------------------------------------------------- vab_source_routing_board() { local src_id local dst_id if [[ "${FORMAT}" == "html" ]] then echo "

" echo "
" echo "

" echo "" echo "

Source Routing Board

" echo "" echo " " echo -n " " else echo "TABLE: Source_Routing_Board" echo -n "#;Type;IP;Host Name;Status;Device;" fi for dst_id in ${NETWORK_DST_ID_LIST} do network_dst_tab_get "${dst_id}" if [[ ( "${ADMIN}" == "true") || ( "${dst_type}" != "1") ]] then if [[ "${FORMAT}" == "html" ]] then echo -n "" else echo -n "${dst_name};" fi fi done if [[ "${FORMAT}" == "html" ]] then echo "" else echo "Port Range;From Port;To Port;Owner;Bytes In;Bytes Out;UpTime;Last Seen" fi for src_id in ${NETWORK_SRC_ID_LIST} do vab_source_routing_board_line "${src_id}" done if [[ "${FORMAT}" == "html" ]] then echo "
#TypeIPHost NameDeviceStatus${dst_name}Port RangeFrom PortTo PortOwnerBytes InBytes OutUpTimeLast Seen
" echo "

" echo "
" echo "

" else echo "" fi } #----------------------------------------------------------------------------------------------------------------------------------- # VPN OpenVPN Board #----------------------------------------------------------------------------------------------------------------------------------- vab_vpn_openvpn_board() { local src_id local class local idx=0 if [[ "${FORMAT}" == "html" ]] then echo "

" echo "
" echo "

" echo "" echo "

OpenVPN Board

" echo "" echo " " echo " " else echo "TABLE: OpenVPN_Board" echo "#;IP;Host Name;Certificate" fi for src_id in ${NETWORK_SRC_ID_LIST} do network_src_tab_get "${src_id}" if [[ "${src_type}" == "2" ]] then if [[ ( "${USER}" == "" ) || ( "${USER}" == "owner") || ( "${USER}" == "${src_owner}") ]] then if [[ ( "${ADMIN}" == "true") || ( "${REMOTE_USER}" == "${src_owner}") ]] then class="default" else if [[ "${USER}" == "owner" ]] then class="skip" else class="dark" fi fi else class="skip" fi if [[ ( "${STATUS}" != "" ) && ( "${STATUS}" != "${src_status}") ]] then class="skip" fi if [[ "${class}" != "skip" ]] then if [[ "${FORMAT}" == "html" ]] then echo -n " " echo -n "" if [[ ( "${ADMIN}" == "true") || ( "${REMOTE_USER}" == "${src_owner}") ]] then echo -n "" else echo -n "" fi echo -n "" if [[ ( "${ADMIN}" == "true") || ( "${REMOTE_USER}" == "${src_owner}") ]] then echo -n "" else echo -n "" fi echo -n "" if [[ ( "${ADMIN}" == "true") || ( "${REMOTE_USER}" == "${src_owner}") ]] then echo -n "" if [[ -f "/etc/openvpn/tls/certs/${src_host_name}.crt" ]] then echo -n "" echo -n "" echo "" else echo -n "" echo -n "" echo "" fi else echo -n "" if [[ -f "/etc/openvpn/tls/certs/${src_host_name}.crt" ]] then echo -n "" echo -n "" echo "" else echo -n "" echo -n "" echo "" fi fi else echo -n "${idx};${src_ip};${src_host_name}" if [[ -f "/etc/openvpn/tls/certs/${src_host_name}.crt" ]] then echo ";1" else echo ";0" fi fi idx=$(( idx + 1 )) fi fi done if [[ "${FORMAT}" == "html" ]] then echo "
#IPHost NameConfigurationCertificates
Default Route VPNNo Default Route VPNCA Certificate (.crt)TC Certificate (.key)Private Key (.key)Cerificate Signing Request (.csr)Public Certificate (.crt)
External CrtInline CrtExternal CrtInline Crt
${idx}${src_ip}${src_host_name}\"Configuration\"Configuration\"Configuration\"Configuration\"Configuration\"Configuration\"CA\"TC\"Private\"Certificate\"Public
\"Private\"Certificate\"Public
\"TC\"Private\"Certificate\"Public
\"Private\"Certificate\"Public
" echo "

" echo "
" echo "

" fi } #----------------------------------------------------------------------------------------------------------------------------------- # Main Board Print #----------------------------------------------------------------------------------------------------------------------------------- vab_main_board_print() { if [[ "${FORMAT}" == "html" ]] then echo "" if [[ "${ADMIN}" == "true" ]] then ADMIN_MODE=" - Admin Mode" else ADMIN_MODE="" fi case "${USER}" in "") USER_MODE="All" ;; "owner") USER_MODE="My" ;; *) USER_MODE="${USER}" ;; esac case "${STATUS}" in "") STATUS_MODE="All" ;; "0") STATUS_MODE="Down" ;; "1") STATUS_MODE="Up" ;; "2") STATUS_MODE="Not Connected" ;; *) STATUS_MODE="Unknown" ;; esac echo "

VPN Admin Board: ${USER_MODE} VPN - ${STATUS_MODE} Status${ADMIN_MODE}

" echo "

" echo "
" echo "" echo " Date: $(/bin/date)
" echo "

" echo "
" echo "" fi vab_destination_status_board vab_source_routing_board vab_vpn_openvpn_board if [[ "${FORMAT}" == "html" ]] then local time_out local elaps local elaps_sec local elaps_mili echo "

" echo "
" echo "
" echo -n " " if [[ " ${ADMIN_USER_LIST} " == *" ${REMOTE_USER} "* ]] then if [[ "${ADMIN}" == "true" ]] then echo -n "Non Admin Mode" else echo -n "Admin Mode" fi echo -n "   " fi if [[ "${USER}" != "" ]] then echo -n "All VPN" echo -n "   " fi if [[ "${STATUS}" != "" ]] then echo -n "All Status" fi echo "" echo "

" time_out=$(date +%s%N) elaps=$(( TIME_IN - time_out )) elaps_sec=$(( elaps / 1000000000 )) elaps_mili=$(( ( elaps / 1000000) - ( elaps_sec * 1000) )) echo "

" echo "
" echo "
" printf " Page generated in %d.%03d seconds\n" "${elaps_sec}" "${elaps_mili}" echo "

" echo "" echo "
" echo "" echo "

" echo " Rx3 Admin" echo "

" echo "" echo "
" echo "" echo "

" echo " \"Best" echo " \"Valid" echo " \"Valid" echo "

" fi } #----------------------------------------------------------------------------------------------------------------------------------- # Query String Parse #----------------------------------------------------------------------------------------------------------------------------------- vab_query_string_parse() { local var local arg local i CMD="" FORMAT="" USER="" STATUS="" IP="" VPN="" TYPE="" REDIRECT="" CMD_STATUS="" ADMIN="" FILTER="" DEFROUTE="" FILENAME="" if [[ "${QUERY_STRING}" != "" ]] then local OIFS="${IFS}" IFS="&" set ${QUERY_STRING} IFS="${OIFS}" i=$# while [[ "${i}" != "0" ]] do var="${1/=*/}" arg="${1/*=/}" case "${var}" in "cmd") CMD="${arg}" ;; "format") FORMAT="${arg}" ;; "admin") ADMIN="${arg}" ;; "filter") FILTER="${arg}" ;; "user") USER="${arg}" ;; "status") STATUS="${arg}" ;; "ip") IP="${arg}" ;; "vpn") VPN="${arg}" ;; "type") TYPE="${arg}" ;; "filename") FILENAME="${arg}" ;; "defroute") DEFROUTE="${arg}" ;; esac shift i=$(( i - 1 )) done fi if [[ "${FORMAT}" == "" ]] then FORMAT="html" fi } #----------------------------------------------------------------------------------------------------------------------------------- # Command Handler #----------------------------------------------------------------------------------------------------------------------------------- vab_command_handler() { if [[ ( "${ADMIN}" == "true") && ( " ${ADMIN_USER_LIST} " != *" ${REMOTE_USER} "*) ]] then CMD_STATUS="${CMD}: Admin NOT_AUTHORIZED" REDIRECT="?user=${USER}&up=${up}" vab_header_print vab_footer_print else case "${CMD}" in "") vab_header_print vab_main_board_print vab_footer_print ;; "route_set") network_src_tab_ip_lookup "${IP}" network_src_tab_get "${src_id}" network_dst_tab_get "${VPN}" if [[ ( "${ADMIN}" == "true") || ( ( "${REMOTE_USER}" == "${src_owner}") && ( "${dst_type}" != "1")) ]] then sudo rx3_net_adm table_set "${IP}" "${dst_table}" 1>&2 if [[ "$?" == "0" ]] then CMD_STATUS="route_set: OK" else CMD_STATUS="route_set: KO" fi else CMD_STATUS="route_set: NOT_AUTHORIZED [${REMOTE_USER}]/[${src_owner}]/[${dst_type}]" fi REDIRECT="?admin=${ADMIN}&filter=${FILTER}" vab_header_print vab_footer_print ;; "cert_download") network_src_tab_ip_lookup "${IP}" network_src_tab_get "${src_id}" if [[ ( "${ADMIN}" == "true") || ( "${REMOTE_USER}" == "${src_owner}") || ( "${TYPE}" == "ca") || ( "${TYPE}" == "crt") ]] then CMD_STATUS="cert_download: OK" FORMAT="txt" case "${TYPE}" in "ca") FILE_NAME="ca.crt" HOST_NAME="" ;; "tc") FILE_NAME="tc.key" HOST_NAME="" ;; *) HOST_NAME="$( host "${IP}" | sed -e 's/.*domain name pointer //' -e 's/.$//')" FILE_NAME="${HOST_NAME}.${TYPE}" ;; esac vab_header_print sudo cert_dump "${TYPE}" "${HOST_NAME}" else CMD_STATUS="cert_download: NOT_AUTHORIZED" REDIRECT="?admin=${ADMIN}&filter=${FILTER}" FORMAT="html" vab_header_print vab_footer_print fi ;; "config_download") network_src_tab_ip_lookup "${IP}" network_src_tab_get "${src_id}" if [[ ( "${ADMIN}" == "true") || ( "${REMOTE_USER}" == "${src_owner}") || ( "${TYPE}" == "ext") ]] then local defroute_pipe local template_name local route_type CMD_STATUS="config_download: OK" HOST_NAME="$( host "${IP}" | sed -e 's/.*domain name pointer //' -e 's/.$//')" template_name="rx3-client.ovpn" if [[ "${DEFROUTE}" == "false" ]] then defroute_pipe="sed s/#pull-filter/pull-filter/" route_type="nodefroute" else defroute_pipe="cat" route_type="defroute" fi FORMAT="txt" if [[ "${TYPE}" == "ext" ]] then FILE_NAME="${HOST_NAME}-${route_type}-external.ovpn" vab_header_print sed \"; sudo cert_dump ca; echo \"<\/ca>\")/" \ -e "s/cert tls\/certs\/CLIENT_FQDN.crt/\$(echo \"\"; sudo cert_dump crt CLIENT_FQDN; echo \"<\/cert>\")/" \ -e "s/key tls\/private\/CLIENT_FQDN.key/\$(echo \"\"; sudo cert_dump key CLIENT_FQDN; echo \"<\/key>\")/" \ -e "s/tls-crypt tls\/private\/tc.key/\$(echo \"\"; sudo cert_dump tc; echo \"<\/tls-crypt>\")/" \ -e "s/CLIENT_FQDN/${HOST_NAME}/g")\"" | ${defroute_pipe} fi else CMD_STATUS="config_download: NOT_AUTHORIZED" REDIRECT="?admin=${ADMIN}&filter=${FILTER}" FORMAT="html" vab_header_print vab_footer_print fi ;; *) CMD_STATUS="${CMD}: UNKNOWN_CMD" vab_header_print vab_footer_print ;; esac fi } #----------------------------------------------------------------------------------------------------------------------------------- # Main #----------------------------------------------------------------------------------------------------------------------------------- vab_query_string_parse network_init vab_command_handler network_deinit