added a json file to CConfigure

pull/1/head
Tom Early 3 years ago
parent cd30b7f771
commit 95e8c4d07a

9
.gitignore vendored

@ -1,12 +1,7 @@
*.o *.o
*.d *.d
.vscode .vscode
reflector/urfd.blacklist reflector/urfd.*
reflector/urfd.whitelist urfd
reflector/urfd.interlink
reflector/urfd.terminal
configure.mk
wiresx/configure.php
urfd*
inicheck inicheck
dbutil dbutil

@ -2,43 +2,25 @@
The URF Multiprotocol Gateway Reflector Server, ***urfd***, is part of the software system for a Digital Voice Network. The sources are published under GPL Licenses. The URF Multiprotocol Gateway Reflector Server, ***urfd***, is part of the software system for a Digital Voice Network. The sources are published under GPL Licenses.
# Information about this fork
This fork of URFD supports *all* modes currently used in ham radio: D-Star, DMR, YSF, P25, NXDN, M17, and USRP (for connections to AllStar nodes, etc). All transcoding is centralized so there is no double transcoding to/from any mode. This fork of urfd, along with the swambe2 branch of my tcd repo, contains many changes from the original:
Integraded P25 Reflector with software imbe vocoder.
Integrated NXDN Reflector
Inegrated USRP Reflector
Optional software vocoding of AMBE+2(DMR/YSF/NXDN) can be done using md380_vocoder library. This means that only 1 USB dv dongle is required per module. This also makes an ARM platform (like Rpi) a reqirement. See the tcd README for details.
Numerous fixes like late entry recognition from modes like YSF that are otherwise ignored by the original reflector when no header has been received.
The USRP Clients are read from a file defined in Main.h. The format of this file is ipaddr;port; one host per line, ex:
```bash
192.168.1.100;32000;
192.168.1.101;32001;
```
The rest of this README is unchanged from the original.
## Introduction ## Introduction
This will build a new kind of digital voice reflector. Based on N7TAE's [new-xlxd](https://github.com/n7tae/new-xlxd), which, in turn, is based on the first multi-protocol reflector, [xlxd](https://github.com/LX3JL/xlxd), **urfd** supports all protocols of it's predecessors, as well as both M17 protocols, **voice-only** and **voice+data**! A key part of this is the hybrid transcoder, [tcd](https://github.com/n7tae/tcd), which is in a seperate repository. URFd is not compatible with either new-xlxd or xlxd. You can't interlink urfd with xlxd. This reflector can be built without a transcoder, but clients will only hear other clients using the same protocol. Please note that currently, urfd only supports the tcd transcoder when run locally. For best performance, urfd and tcd uses UNIX DGRAM sockets for interprocess communications. These kernel-base sockets are signifantly faster than conventional UDP/IP sockets. It should be noted that tcd supports DVSI-3003 nad DVSI-3000 devices, which it uses for AMBE vocoding. This will build a new kind of digital voice reflector. A *urfd* supports DStar protocols (DPlus, DCS, DExtra and G3) DMR protocols (MMDVMHost, DMR+ and NXDN), M17, YSF, P25 (using IMBE) and USRP (Allstar). A key part of this is the hybrid transcoder, [tcd](https://github.com/n7tae/tcd), which is in a seperate repository. You can't interlink urfd with xlxd. This reflector can be built without a transcoder, but clients will only hear other clients using the same codec. Please note that currently, urfd only supports the tcd transcoder when run locally. As a local device, urfd and tcd uses UNIX DGRAM sockets for interprocess communications. These kernel-base sockets are signifantly faster than conventional UDP/IP sockets. It should be noted that tcd supports DVSI-3003 nad DVSI-3000 devices, which it uses for AMBE vocoding.
This build support *dual-stack* operation, so the server on which it's running, must have both an IPv4 and IPv6 routable address if you are going to configure a dual-stack reflector.
This build support *dual-stack* operation, so the server on which it's running, must have both an IPv4 and IPv6 routable address if you are going to configure a dual-stack reflector. URF can support out-going DExtra links, by adding a new DExtra Peer type *and* it has many changes designed to increase reliability and stability. There are many improvements previous multi-mode reflectors:
There are many improvements of urfd over xlxd, some of which were inherited from new-xlxd:
- Nearly all std::vector containers have been replaced with more appropriate containers. - Nearly all std::vector containers have been replaced with more appropriate containers.
- No classes are derived from any standard containers. - No classes are derived from any standard containers.
- For concurancy, *i.e.*, thread management, the standard thread (std::thread) library calls have been replaced with std::future. - For concurancy, *i.e.*, thread management, the standard thread (std::thread) library calls have been replaced with std::future.
- Managed memory, std::unique_ptr and std::shared_ptr, is used replacing the need for calls to *new* and *delete*. - Managed memory, std::unique_ptr and std::shared_ptr, is used replacing the need for calls to *new* and *delete*.
- Your reflector can be configured with up to 26 modules, *A* through *Z* and as few as one module. For other choices, the configure modules don't have to be contigious. For example, you could configure modules A, B, C and E. - Your reflector can be configured with up to 26 modules, *A* through *Z* and as few as one module. For other choices, the configure modules don't have to be contigious. For example, you could configure modules A, B, C and E.
- An integraded P25 Reflector with software imbe vocoder.
- An integrated NXDN Reflector
- An inegrated USRP Reflector
Only systemd-based operating systems are supported. Debian or Ubuntu is recommended. If you want to install this on a non-systemd based OS, you are on your own. Also, by default, tcd and urfd are built without gdb support. You can add debugging support in the configuration script, `./rconfig`. Finally, this repository is designed so that you don't have to modify any file in the repository when you build your system. Any file you need to modify to properly configure your reflector will be a file you copy from you locally cloned repo. This makes it easier to update the source code when this repository is updated. Follow the instructions below to build your transcoding URF reflector. Only systemd-based operating systems are supported. Debian or Ubuntu is recommended. If you want to install this on a non-systemd based OS, you are on your own. Finally, this repository is designed so that you don't have to modify any file in the repository when you build your system. Any file you need to modify to properly configure your reflector will be a file you copy from you locally cloned repo. This makes it easier to update the source code when this repository is updated. Follow the instructions below to build your transcoding URF reflector.
## Usage ## Usage
@ -66,47 +48,44 @@ sudo apt upgrade
sudo apt install git sudo apt install git
sudo apt install apache2 php5 sudo apt install apache2 php5
sudo apt install build-essential sudo apt install build-essential
sudo apt install libmariadb-dev-compat sudo apt install nlohman-json3-dev
``` ```
### YSF direct connection support ### Download and build the repository and
The following is needed if you plan on supporting local YSF frequency registration database for those YSF-clients that want to directly connect to URF. You will also need to install the client frequency registration pages on your web server. This is because the WiresX protocol supplies the operational frequency to connecting clients.
```bash
sudo apt install php-mysql mariadb-server mariadb-client
```
### Download the repository(s)
```bash ```bash
git clone https://github.com/n7tae/urfd.git git clone https://github.com/n7tae/urfd.git
cd urfd/reflector
``` ```
And, if needed, the hybrid transcoder: You'll need to set two compile-time options:
```bash ```bash
git clone https://github.com/n7tae/tcd.git cp example.mk configure.mk
``` ```
### Create and edit your blacklist, whitelist and linking files This will disable debugging and define where the Makefile will install *urfd*.
If you want to change this, edit the configure.mk file with your favorite text editor. Then do a `make` to build *urfd*.
First, move to the reflector build directory: ### Create and edit your configuration files
First, move to the reflector build directory and create your configuration file:
```bash ```bash
cd urfd/reflector cd urfd/reflector
```
The blacklist file defines callsigns that are blocked from linking or transmitting. The whitelist file defines callsigns that are allowed to link and transmit. Both of these files support the astrisk as a wild-card. The supplied blacklist file is empty and the supplied whitelist file contains a single definition, \*, which will allow any callsign to link and transmit, blocking no one. The interlink file defines possible Brandmeister, XRF and URF linking. The terminal file defines operations for Icom's Terminal and Access Point mode, sometimes called *G3*. This protocol requires significantly higher connection resources than any other mode, so it is possible to build a URF reflector without G3 support.
```bash cp ../config/* .
cp ../config/urfd.blacklist .
cp ../config/urfd.whitelist .
cp ../config/urfd.interlink .
cp ../config/urfd.terminal .
``` ```
If you are not going to support G3 linking, you don't need to copy the .terminal file. Use your favorite editor to modify each of these files. If you want a totally open network, the blacklist and whitelist files are ready to go. The blacklist determine which callsigns can't use the reflector. The whitelist determines which callsigns can use the reflector. The interlink file sets up the URF<--->URF inter-linking and/or out-going XRF peer linking. This will create seven files:
1. The `urfd.blacklist` file defines callsigns that are blocked from linking or transmitting.
2. The `urfd.whitelist` file defines callsigns that are allowed to link and transmit. Both of these files support the astrisk as a wild-card. The supplied blacklist and whitelist file are empty, which will allow any callsign to link and transmit, blocking no one.
3. The `urfd.interlink` file defines possible Brandmeister and URF linking.
4. The `urfd.terminal` file defines operations for Icom's Terminal and Access Point mode, sometimes called *G3*. This protocol requires significantly higher connection resources than any other mode, so it is possible to build a URF reflector without G3 support.
5. The `urfd.service` file is a systemd file that will start and stop *urfd*. Importantly, it contains the only reference to where the *urfd* initialization file is located.
6. The `urfd.mk` file contains compile-time options for *urfd*. If you change the `BINDIR`, you'll need to update how `urfd.service` starts *urfd*.
7. The `urfd.ini` file contains the run-time options for *urfd* and will be discussed below.
When you are done with the configuration files and ready to start the installation process, you can return to the main repository directory: When you are done with the configuration files and ready to start the installation process, you can return to the main repository directory:
@ -114,31 +93,48 @@ When you are done with the configuration files and ready to start the installati
cd .. cd ..
``` ```
### Build *urfd*
After possibly editing `urfd.mk`, you can build your reflector: `make` . Besides building *urfd*, this will also build two helper applications that will be discussed below.
### Configuring your reflector ### Configuring your reflector
Configuring, compiling and maintaining your reflector build is easy! Start the configuration script in the base directory of you cloned repo: Use your favorite text editor to set your run-time configuration in your copy of `urfd.ini`.
```bash There are only a few things that need to be specified. Most important are, the reflector callsign and the IP addresses for the IPv4 and IPv6 listen ports and a transcoder port, if there is a transcoder. Dual-stack operation is enabled by specifying both an IPv4 and IPv6 address. IPv4-only single stack can be specified by leaving the IPv6 address undefined.
./rconfig
``` You can configure any modules, from **A** to **Z**. They don't have to be contigious. If your reflector is configured with a transcoder, you can specify which configured modules will be transcoded. Up to three modules can be transcoded if you have the necessary hardware.
Three protocols, BrandMeister, G3 and USRP should be disabled if you aren't going to use them.
There are only a few things that need to be specified. Most important are, the reflector callsign and the IP addresses for the IPv4 and IPv6 listen ports and a transcoder port, if there is a transcoder. Dual-stack operation is enabled by specifying both an IPv4 and IPv6 address. IPv4-only single stack can be specified by leaving the IPv6 address set to `none`. It's even possible to operate in an IPv6-only configuration by leaving the IPv4 address to the default `none`. For most users, you can define the IP addresses as "any", but you can specify specific IPv4 and IPv6 addresses, if this is required for you installation site. There are three databases needed by *urfd*:
1. The *DMR ID* database maps a DMR ID to a callsign and *vis versa*.
2. The *NXDN ID* database maps an NXDN ID to a callsign and *vis versa*.
3. The *YSF Tx/Rx* database maps a callsign to a transmit/receive RF frequencies.
These databases can come from a URL or a file, or both. If you specify "both", then the file will be read after the URL.
You can configure any modules, from **A** to **Z**. They don't have to be contigious. If your reflector is configured with a transcoder, you can specify which configured modules will be transcoded. Up to three modules can be transcoded. There are also true/false flags to prevent G3 support and so that you can build executables that will support gdb debugging. ### Helper apps
You can support your own YSF frequency database. This is very useful for hot-spots that use YSF linking. These linked hot-spots can then use the *WiresX* command on their radios to be able to connect to any configured URF module. Users can register their TX and RX frequency (typically the same for most hot-spot configurations) on http:<*urf url*>/wiresx/login.php. Once their hot-spot is registered, URF will return the correct frequency for their hot-spot when a *WiresX* command is sent to the reflector. You'll need to enable YSF auto-linking, specify a default module and define a database name, user and user password. When you write you URF configuration, a database **configure.sql** script will be built to not only create the database and database user, but also the table for the hot-spot frequency data. There are two, very useful helper applications, *inicheck* and *dbutil*. Both apps will show you a usage message if you execuate them without any arguments.
Be sure to write out the configuration files and look over the up to seven different configration files that are created. The first file, reflector.cfg is the memory file for rconfig so that if you start that script again, it will remember how you left things. There are one or two `.h` files for the reflector and tcd and there are one or two `.mk` files for the reflector and tcd makefiles. You should **not** modify these files by hand unless you really know exactly how they work. The rconfig script will not start if it detects that an URF server is already running. You can override this behavior in expert mode: `./rconfig expert`. If you do change the configuration after you have already compiled the code, it is safest if you clean the repo and then recompile. The *inicheck* app will use the exact same code that urfd uses to validate your `urfd.ini` file. Do `./inicheck -q mrefd.ini` to check your infile for errors. If you see any messages containing `ERROR`, that means that *urfd* won't start. You'll have to fix the errors described in the message(s). If you only see messages containing `WARNING`, *urfd* will start, but it may not perform as expected. You will have to decide if the warning should be fixed. If you don't see any messages, it means that your ini file is syntactly correct.
### Compling and installing your system The *dbutil* app can be used for serveral tasks relating to the three databases that *urfd* uses. The usage is: `./dbutil DATABASE SOURCE ACTION INIFILE`, where:
- DATABASE is "dmr", "nxdn" or "ysf"
- SOURCE is "html" or "file"
- ACTION is "parse" or "errors"
- INIFLILE is the path to the infile that defines the location of the http and file sources for these three databases.
One at a time, *dbutil* can work with any of the three DATABASEs. It can read either the http or the file SOURCE. It can either show you the data entries that are syntactically correct or incorrect (ACTION).
After you have written your configutation files, you can build and install your system: ### installing your system
After you have written your configutation files, you can install your system:
```bash ```bash
./radmin ./radmin <PATH TO INI FILE>
``` ```
Use this command to compile and install your system. It can also be used to uninstall your system. It will use the information in reflector.cfg to perform each task. This radmin menu can also perform other tasks like restarting the reflector or transcoder process. It can also be used to update the software, if the system is uninstalled. Use can use this interactive shell script to install and uninstall your system. This can also perform other tasks like restarting the reflector or transcoder process, or be used to view the reflector or transcoder log in real time.
### Stoping and starting the services manually ### Stoping and starting the services manually
@ -163,39 +159,26 @@ Please note that your www root directory might be some place else. There is one
**DO NOT** enable the "calling home" feature unless you are sure that you will not be infringing on an existing XLX or XRF reflector with the same callsign suffix. If you don't understand what this means, don't set `$CallingHome['Active']` to true! **DO NOT** enable the "calling home" feature unless you are sure that you will not be infringing on an existing XLX or XRF reflector with the same callsign suffix. If you don't understand what this means, don't set `$CallingHome['Active']` to true!
If you have configured support of hot-spot frequency registation, recursively copy the **wiresx** directory where the index.php file is for your dashboard. Also from the build directory, create the database and database user and hot-spot frequency table:
```bash
sudo mysql < configure.sql
```
The configure.sql file will be generated automatically by the rconfig script **if** you have enabled the **YSF Local Database**.
## Updating urfd and tcd
Updating can be performed entirely in the radmin script, but just in case there is a new version of the radmin script, you can start first with a simple `git pull`. If any .h or .cpp fiiles have updates, you can then start radmin and do a clean and compile and then uninstall and install: `cl, co, us, is`. Follow that with a `rl` to watch the reflector log, or an `rt` to watch the transcoder while it comes up.
If rconfig was updated with the `git pull`, it might be wise to run it first to see if there have been any new options added to the code base. If so, be sure to write out the new configuration files before exiting rconfig. THen you can rebuild and reinstall your reflector.
If you change any configuration after your reflector has been compiled, be sure to do a clean/compile/uninstall/reinstall to sync your system to the new configuration.
## Firewall settings ## Firewall settings
URF Server requires the following ports to be open and forwarded properly for in- and outgoing network traffic: URF Server requires the following ports to be open and forwarded properly for in- and outgoing network traffic. Obviously you don't need to open ports for G3, USRP and BrandMeister if they are not enabled:
```text ```text
TCP port 80 (http) optional TCP port 443 (https) TCP port 80 (http) optional TCP port 443 (https)
UDP port 8880 (DMR+ DMO mode)
UDP port 10002 (BM connection) UDP port 10002 (BM connection)
UDP port 10017 (URF interlinking) UDP port 10017 (URF interlinking)
UDP port 42000 (YSF protocol) UDP port 12345 - 12346 (G3 Icom Terminal presence and request port)
UDP port 17000 (M17 protocol) UDP port 17000 (M17 protocol)
UDP port 30001 (DExtra protocol)
UPD port 20001 (DPlus protocol) UPD port 20001 (DPlus protocol)
UDP port 30001 (DExtra protocol)
UDP port 30051 (DCS protocol) UDP port 30051 (DCS protocol)
UDP port 8880 (DMR+ DMO mode) UDP port 32000 (USRP protocol)
UDP port 40000 (G3 Icom Terminal port)
UDP port 41000 (P25 port)
UDP port 41400 (NXDN port)
UDP port 42000 (YSF protocol)
UDP port 62030 (MMDVM protocol) UDP port 62030 (MMDVM protocol)
UDP port 12345 - 12346 (Icom Terminal presence and request port)
UDP port 40000 (Icom Terminal dv port)
``` ```
## YSF Master Server ## YSF Master Server
@ -207,9 +190,9 @@ It has nothing to do with the regular YSFReflector network, hence you dont ne
I will eventually support a remote transcoder option, so that you can, for example, run urfd in a data center, and then run the transcoder somewhere you have physical access to it so you can plug in your AMBE vocoders. I don't recommend this as it will add unnessary and variable latency to your reflector. I will eventually support a remote transcoder option, so that you can, for example, run urfd in a data center, and then run the transcoder somewhere you have physical access to it so you can plug in your AMBE vocoders. I don't recommend this as it will add unnessary and variable latency to your reflector.
The M17 team will be working on big changes for the dashboard. I can't wait to see what they come up with! The M17 team will be working on big changes for the dashboard!
## Copyright ## Copyright
- Copyright © 2016 Jean-Luc Deltombe LX3JL and Luc Engelmann LX1IQ - Copyright © 2016 Jean-Luc Deltombe LX3JL and Luc Engelmann LX1IQ
- Copyright © 2021 Thomas A. Early N7TAE - Copyright © 2022 Doug McLain AD8DP and Thomas A. Early N7TAE

@ -118,8 +118,9 @@ RefreshMin = 191
######### Other File locations ######### Other File locations
[Files] [Files]
PidPath = /var/run/urfd.pid PidPath = /var/run/xlxd.pid
ReflStatePath = /var/log/urfd.xml XmlPath = /var/log/xlxd.xml
#JsonPath = /var/tmp/urfd.json # for future development
WhitelistPath = /home/user/urfd.whitelist WhitelistPath = /home/user/urfd.whitelist
BlacklistPath = /home/user/urfd.blacklist BlacklistPath = /home/user/urfd.blacklist
InterlinkPath = /home/user/urfd.interlink InterlinkPath = /home/user/urfd.interlink

@ -57,28 +57,6 @@ InstallReflector () {
popd popd
} }
Clean () {
pushd reflector
make clean
popd
pushd ../tcd
make clean
popd
}
Compile () {
local np
np=`getconf _NPROCESSORS_ONLN`
pushd reflector
make -j$np || read -p "<Enter to continue: " ans
popd
if [[ "$tcaddress" != none ]]; then
pushd ../tcd
make -j$np || read -p "<Enter to continue: " ans
popd
fi
}
# Execution starts here! # Execution starts here!
# service file locations # service file locations
@ -118,10 +96,6 @@ do
git status | head -1 git status | head -1
echo echo
echo "ls : List the configuration file" echo "ls : List the configuration file"
echo "cl : Clean (remove) compiled objects and executables"
echo "gp : Pull the latest software from the repo"
echo "br : Change git branch to <value>"
echo "co : Compile the system executable(s)"
if [ -e $urfserv ]; then if [ -e $urfserv ]; then
if [ -e $urfserv ]; then if [ -e $urfserv ]; then
echo "us : Uninstall the URF reflector" echo "us : Uninstall the URF reflector"
@ -164,33 +138,11 @@ do
if [ -e reflector/urfd ] && [ ! -e $urfserv ]; then if [ -e reflector/urfd ] && [ ! -e $urfserv ]; then
InstallReflector InstallReflector
fi fi
elif [[ "$key" == gp* ]]; then
echo
git pull
echo
read -p "<Enter> to continue: " ans
pushd ../tcd
git pull
echo
read -p "<Enter> to continue: " ans
popd
elif [[ "$key" == br* ]]; then
echo
git checkout "$value"
echo
read -p "<Enter> to continue: " ans
pushd ../tcd
git checkout "$value"
echo
read -p "<Enter> to continue: " ans
popd
elif [[ "$key" == rr* ]]; then elif [[ "$key" == rr* ]]; then
if [ -e $urfserv ]; then if [ -e $urfserv ]; then
sudo systemctl restart urfd sudo systemctl restart urfd
fi fi
elif [[ "$key" == tr* ]] && [ -e $tcdserv ]; then sudo systemctl restart tcd elif [[ "$key" == tr* ]] && [ -e $tcdserv ]; then sudo systemctl restart tcd
elif [[ "$key" == cl* ]]; then Clean
elif [[ "$key" == co* ]]; then Compile
elif [[ "$key" == tl* ]] && [ -e $tcdserv ]; then sudo journalctl -u tcd -f elif [[ "$key" == tl* ]] && [ -e $tcdserv ]; then sudo journalctl -u tcd -f
elif [[ "$key" == rl* ]] && [ -e $urfserv ]; then sudo journalctl -u urfd -f elif [[ "$key" == rl* ]] && [ -e $urfserv ]; then sudo journalctl -u urfd -f
fi fi

@ -1,653 +0,0 @@
#!/bin/bash
#
# Copyright (c) 2021 by Thomas A. Early N7TAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#!/bin/bash
CharCount () {
local haystack=$1
local test="\${haystack//[^$2]}"
eval local result=$test
return ${#result}
}
RemoveDupes () {
local -n s=$1
local i j
for (( i=0; i<${#s}-1; i++)); do
for (( j=$i+1; j<${#s}; j++)); do
if [[ ${s:$i:1} != "." ]]; then
if [[ ${s:$i:1} == ${s:$j:1} ]]; then
local l=$(($j + 1))
s="${s:0:$j}.${s:$l}"
fi
fi
done
done
s=${s//.}
}
CheckModules () {
# only A through Z
modules=${1//[^A-Z]}
if (( ${#modules} < 1 )); then
unset modules
clear
echo "ERROR: You must specify at least one module, A to Z!"
echo
read -p "<Enter> to continue: " ans
return
fi
RemoveDupes modules
tcmodules=${modules:0:1}
}
CheckTranscodedModules () {
local tc=${1//[^A-Z]}
local tcorig=$tc
if (( ${#tc} < 1 )); then
tcmodules=${modules:0:1}
clear
echo "ERROR: You must specify at least one module!"
echo "If you aren't using a transcoder, set the transcoder"
echo "address value to the default 'none'!"
echo
read -p "<Enter> to continue: " ans
return
fi
RemoveDupes tc
local m=$modules_d
if [ ! -z ${modules+x} ]; then
m=$modules
fi
local i
for ((i=0; i<${#tc}; i++)); do
CharCount $m ${tc:$i:1}
local count=$?
if (( $count < 1 )); then
local j=$((i+1))
tc="${tc:0:$i}.${tc:$j}"
fi
done
tc=${tc//.}
if (( ${#tc} < 1 )); then
echo "ERROR: Module(s), '$tcorig', are not in the configure modules, '$m'!"
echo
read -p "<Enter> to continue: " ans
tcmodules=${modules:0:1}
return
fi
tcmodules=$tc
}
SetBooleanValue ()
{
local dvname
local cv
if [ -z $2 ]; then
if [ -z ${!1+x} ]; then
if [[ "$1" == module_[abc]_* ]]; then
echo matches
dvname=${1//_[abc]_/_x_}
else
echo does not match
dvname=${1}_d
fi
cv=${!dvname}
else
cv=${!1}
fi
if [[ $cv == [tT]* ]]; then
eval ${1}=false
else
eval ${1}=true
fi
elif [[ "$2" == [tT]* ]]; then
eval ${1}=true
else
eval ${1}=false
fi
}
EvaluateVar ()
{
if [ -z ${!1+x} ]; then
if [ -z "${!2}" ]; then
echo "'' <DEFAULT>"
else
echo "${!2} <DEFAULT>"
fi
else
if [ -z "${!1}" ]; then
echo "''"
else
echo "${!1}"
fi
fi
}
TestGainValue ()
{
if [[ "$1" =~ ^[-]?[0-9]+$ ]]; then
if (( $1 >= -24 && $1 <= 24 )); then
return 0
else
return 1
fi
else
return 1
fi
}
AudioGainMenu ()
{
while [[ "$key" != q* ]]
do
clear
echo
echo " DVSI AMBE Chip Audio Gain Menu"
echo
echo " All values are in dB and can be from -24 to 24"
echo " 'input' means the audio amplitude is adjusted before encoding"
echo " 'output' means the audio amplitude is adjusted after decoding"
echo
echo " Non-zero values are generally not required!"
echo " USE AT YOUR OWN RISK!"
echo
echo -n "si : D-Star audio input = "; EvaluateVar dstar_in_gain{,_d}
echo -n "so : D-Star audio output = "; EvaluateVar dstar_out_gain{,_d}
echo -n "mi : DMR/YSF audio input = "; EvaluateVar dmr_in_gain{,_d}
echo -n "mo : DMR/YSF audio output = "; EvaluateVar dmr_out_gain{,_d}
echo
echo "q : Return to Main Menu"
echo "u : Unset the value of <key> (revert to the default value)."
echo
read -p "Please input <key> <value> : " key value
if [[ "$key" == si* ]]; then
TestGainValue "$value" && dstar_in_gain="$value"
elif [[ "$key" == so* ]]; then
TestGainValue "$value" && dstar_out_gain="$value"
elif [[ "$key" == mi* ]]; then
TestGainValue "$value" && dmr_in_gain="$value"
elif [[ "$key" == mo* ]]; then
TestGainValue "$value" && dmr_out_gain="$value"
elif [[ "$key" == u* ]]; then
if [[ "$value" == si* ]]; then
unset dstar_in_gain
elif [[ "$value" == so* ]]; then
unset dstar_out_gain
elif [[ "$value" == mi* ]]; then
unset dmr_in_gain
elif [[ "$value" == mo* ]]; then
unset dmr_out_gain
fi
fi
done
}
WriteMemFile ()
{
local file
file="$rcfg"
echo "# created on `date`" > $file
[ -z ${callsign+x} ] || echo "callsign='$callsign'" >> $file
[ -z ${modules+x} ] || echo "modules='$modules'" >> $file
[ -z ${ip4addr+x} ] || echo "ip4addr='$ip4addr'" >> $file
[ -z ${ip6addr+x} ] || echo "ip6addr='$ip6addr'" >> $file
[ -z ${tcaddress+x} ] || echo "tcaddress='$tcaddress'" >> $file
[ -z ${tcmodules+x} ] || echo "tcmodules='$tcmodules'" >> $file
[ -z ${dstar_in_gain+x} ] || echo "dstar_in_gain=$dstar_in_gain" >> $file
[ -z ${dstar_out_gain+x} ] || echo "dstar_out_gain=$dstar_out_gain" >> $file
[ -z ${dmr_in_gain+x} ] || echo "dmr_in_gain=$dmr_in_gain" >> $file
[ -z ${dmr_out_gain+x} ] || echo "dmr_out_gain=$dmr_out_gain" >> $file
[ -z ${dmrdbuseserver+x} ] || echo "dmrdbuseserver=$dmrdbuseserver" >> $file
[ -z ${dmrdbrefresh+x} ] || echo "dmrdbrefresh=$dmrdbrefresh" >> $file
[ -z ${dmrdbpath+x} ] || echo "dmrdbpath='$dmrdbpath'" >> $file
[ -z ${ysfautolink+x} ] || echo "ysfautolink=$ysfautolink" >> $file
[ -z ${ysfmodule+x} ] || echo "ysfmodule='$ysfmodule'" >> $file
[ -z ${ysflocaldb+x} ] || echo "ysflocaldb=$ysflocaldb" >> $file
[ -z ${ysfdbname+x} ] || echo "ysfdbname='$ysfdbname'" >> $file
[ -z ${ysfdbuser+x} ] || echo "ysfdbuser='$ysfdbuser'" >> $file
[ -z ${ysfrname+x} ] || echo "ysfrname='$ysfrname'" >> $file
[ -z ${ysfrdesc+x} ] || echo "ysfrdesc='$ysfrdesc'" >> $file
[ -z ${ysfdbpw+x} ] || echo "ysfdbpw='$ysfdbpw'" >> $file
[ -z ${g3support+x} ] || echo "g3support=$g3support" >> $file
[ -z ${dbsupport+x} ] || echo "dbsupport=$dbsupport" >> $file
}
WriteSRCHFile ()
{
local file m
file="$srch"
echo "// Created on `date`" > $file
echo "#define CALLSIGN \"${callsign}\"" >> $file
if [[ "$callsign" == XRF* ]]; then
echo "#define NO_XLX" >> $file
fi
if [ -z ${modules+x} ]; then
echo "#define ACTIVE_MODULES \"${modules_d}\"" >> $file
else
echo "#define ACTIVE_MODULES \"${modules}\"" >> $file
fi
if [ ! -z ${ip4addr+x} ]; then
echo "#define LISTEN_IPV4 \"${ip4addr}\"" >> $file
fi
if [ ! -z ${ip6addr+x} ]; then
echo "#define LISTEN_IPV6 \"${ip6addr}\"" >> $file
fi
if [ -z ${ysfautolink+x} ]; then
echo "#define YSF_AUTOLINK_ENABLE ${ysfautolink_d}" >> $file
else
echo "#define YSF_AUTOLINK_ENABLE ${ysfautolink}" >> $file
fi
if [ -z ${ysfmodule+x} ]; then
echo "#define YSF_AUTOLINK_MODULE '${ysfmodule_d}'" >> $file
else
echo "#define YSF_AUTOLINK_MODULE '${ysfmodule}'" >> $file
fi
if [ ! -z ${dmrdbuseserver+x} ]; then
if [[ "$dmrdbuseserver" == true ]]; then
m=1
else
m=0
fi
else
m=1
fi
echo "#define DMRIDDB_USE_RLX_SERVER $m" >> $file
if [ ! -z ${dmrdbrefresh+x} ]; then
echo "#define DMRIDDB_REFRESH_RATE $dmrdbrefresh" >> $file
else
echo "#define DMRIDDB_REFRESH_RATE $dmrdbrefresh_d" >> $file
fi
if [ ! -z ${dmrdbpath+x} ]; then
echo "#define DMRIDDB_PATH \"$dmrdbpath\"" >> $file
else
echo "#define DMRIDDB_PATH \"$dmrdbpath_d\"" >> $file
fi
if [[ "$ysflocaldb" == true ]]; then
echo '#define YSF_DB_SUPPORT true' >> $file
echo "#define YSF_DB_NAME \"$ysfdbname\"" >> $file
echo "#define YSF_DB_USER \"$ysfdbuser\"" >> $file
echo "#define YSF_DB_PASSWORD \"$ysfdbpw\"" >> $file
else
echo '#define YSF_DB_SUPPORT false' >> $file
fi
if [ ! -z ${ysfrname+x} ]; then
echo "#define YSF_REFLECTOR_NAME \"$ysfrname\"" >> $file
fi
if [ ! -z ${ysfrdesc+x} ]; then
echo "#define YSF_REFLECTOR_DESCRIPTION \"$ysfrdesc\"" >> $file
fi
if [ ! -z ${tcaddress+x} ]; then
echo "#define TRANSCODER_IP \"${tcaddress}\"" >> $file
echo "#define TRANSCODED_MODULES \"${tcmodules}\"" >> $file
fi
if [ -z ${g3support+x} ]; then
m=${g3support_d}
else
m=${g3support}
fi
if [[ "$m" != true ]]; then
echo "#define NO_G3" >> $file
fi
if [ -n ${dbsupport+x} ]; then
if [ "$dbsupport" == true ]; then
echo "#define DEBUG" >> $file
fi
fi
}
WriteSRCMKFile ()
{
local file
file="$srcm"
echo "# Created on `date`" > $file
[ -z ${ip4addr+x} ] || echo "ipv4 = $ip4addr" >> $file
[ -z ${ip6addr+x} ] || echo "ipv6 = $ip6addr" >> $file
[ -z ${tcaddress+x} ] || echo "tc_ip = $tcaddress" >> $file
if [ -z ${g3support+x} ]; then
echo "use_g3 = $g3support_d" >> $file
else
echo "use_g3 = $g3support" >> $file
fi
if [ -z ${dbsupport+x} ]; then
echo "debug = $dbsupport_d" >> $file
else
echo "debug = $dbsupport" >> $file
fi
if [ -z ${ysflocaldb+x} ]; then
echo "ysf_db = $ysflocaldb_d" >> $file
else
echo "ysf_db = $ysflocaldb" >> $file
fi
}
WriteTranscoderHFile ()
{
local file
file="$tcdh"
echo "// Created on `date`" > $file
if [ -n ${tcaddress+x} ] && [[ "$tcaddress" != "local" ]]; then
echo "#define TRANSCODER_IP \"${tcaddress}\"" >> $file
fi
if [ -n ${tcmodules+x} ]; then
echo "#define TRANSCODED_MODULES \"${tcmodules}\"" >> $file
fi
if [ -z ${dstar_in_gain+x} ]; then
echo "#define DSTAR_IN_GAIN $dstar_in_gain_d" >> $file
else
echo "#define DSTAR_IN_GAIN $dstar_in_gain" >> $file
fi
if [ -z ${dstar_out_gain+x} ]; then
echo "#define DSTAR_OUT_GAIN $dstar_out_gain_d" >> $file
else
echo "#define DSTAR_OUT_GAIN $dstar_out_gain" >> $file
fi
if [ -z ${dmr_in_gain+x} ]; then
echo "#define DMR_IN_GAIN $dmr_in_gain_d" >> $file
else
echo "#define DMR_IN_GAIN $dmr_in_gain" >> $file
fi
if [ -z ${dmr_out_gain+x} ]; then
echo "#define DMR_OUT_GAIN $dmr_out_gain_d" >> $file
else
echo "#define DMR_OUT_GAIN $dmr_out_gain" >> $file
fi
if [ -n ${dbsupport+x} ]; then
if [ "$dbsupport" == true ]; then
echo "#define DEBUG" >> $file
fi
fi
}
WriteTranscoderMKFile ()
{
local file
file="$tcdm"
echo "# created on `date`" > $file
if [ -z ${dbsupport+x} ]; then
echo "debug = $dbsupport_d" >> $file
else
echo "debug = $dbsupport" >> $file
fi
}
WriteCfgPhpFile ()
{
cat << EOF > $ysfs
<?php
define('DB_SERVER', 'localhost');
define('DB_USERNAME', '${ysfdbuser}');
define('DB_PASSWORD', '${ysfdbpw}');
define('DB_NAME', '${ysfdbname}');
// Attempt to connect to MySQL database
\$link = mysqli_connect(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);
// Check connection
if (\$link === false) { die("ERROR: Could not connect. " . mysqli_connect_error()); }
?>
EOF
}
WriteDBCreateFile ()
{
cat << EOF > $dbcr
CREATE DATABASE IF NOT EXISTS ${ysfdbname};
USE ${ysfdbname};
CREATE TABLE IF NOT EXISTS ysfnodes (
callsign VARCHAR(7) PRIMARY KEY,
password VARCHAR(255) NOT NULL,
txfreq INT NOT NULL DEFAULT 446500000,
rxfreq INT NOT NULL DEFAULT 446500000,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE USER IF NOT EXISTS '${ysfdbuser}'@'localhost' IDENTIFIED BY '${ysfdbpw}';
GRANT ALL PRIVILEGES ON $ysfdbname . ysfnodes TO '${ysfdbuser}'@'localhost';
FLUSH PRIVILEGES;
EOF
}
WriteCFGFiles ()
{
WriteMemFile
WriteSRCHFile
WriteSRCMKFile
if [ -z ${tcaddress+x} ]; then
rm -f $tcdh $tcdm
else
WriteTranscoderHFile
WriteTranscoderMKFile
fi
if [[ "$ysflocaldb" == true ]]; then
WriteCfgPhpFile
WriteDBCreateFile
else
rm -f $ysfs $dbcr
fi
}
ListCFGFiles ()
{
echo "===========${rcfg}============="
cat $rcfg
echo "===========${srch}============="
cat $srch
echo "===========${srcm}============="
cat $srcm
if [ ! -z ${tcaddress+x} ]; then
echo "===========${tcdh}============="
cat $tcdh
echo "===========${tcdm}============="
cat $tcdm
fi
if [[ "$ysflocaldb" == true ]]; then
echo "===========${ysfs}============="
cat $ysfs
echo "===========${dbcr}============="
cat $dbcr
fi
}
# Execution starts here!
# file locations
rcfg='reflector.cfg'
srch='reflector/configure.h'
srcm='reflector/configure.mk'
tcdh='../tcd/configure.h'
tcdm='../tcd/configure.mk'
ysfs='wiresx/configure.php'
dbcr='configure.sql'
urfserv='/etc/systemd/system/urfd.service'
# expert mode
if [[ "$1" == ex* ]]; then
expertmode="expertMode"
fi
if [ -e reflector.cfg ]; then
source reflector.cfg
else
echo 'No configuration file found...'
sleep 1
fi
# default values
callsign_d='CHNGME'
modules_d='ABCF'
ip4addr_d='none'
ip6addr_d='none'
tcaddress_d='none'
ysfautolink_d=false
ysfmodule_d='D'
g3support_d=false
dbsupport_d=false
dmrdbuseserver_d=true
dmrdbrefresh_d=180
dmrdbpath_d='/usr/local/etc/dmrid.dat'
ysflocaldb_d=false
ysfdbname_d=''
ysfdbuser_d=''
ysfdbpw_d=''
dstar_in_gain_d=0
dstar_out_gain_d=0
dmr_in_gain_d=0
dmr_out_gain_d=0
if [ -z ${callsign+x} ];then
ysfrname_d="$callsign_d"
else
ysfrname_d="$callsign"
fi
ysfrdesc_d='URF Reflector'
if [ -z ${expertmode+x} ]; then
if [ -e $urfserv ]; then
echo -n "You cannot change the configuration right now beacuse there is an "
if [ -e $urfserv ]; then
echo "URFD server running."
fi;
echo "===========${rcfg}============="
cat $rcfg
echo
echo "Please use radmin to uninstall the running server before attempting to modify the configuration."
exit 1
fi
fi
key='x'
# main loop
while [[ "$key" != q* ]]
do
clear
echo
echo " Reflector Configuration, Version #220326"
echo
echo " ******* REFLECTOR ********"
echo -n "cs : Reflector Callsign = "; EvaluateVar callsign{,_d}
echo -n "am : Active Modules = "; EvaluateVar modules{,_d}
echo -n "g3 : Icom G3 Support = "; EvaluateVar g3support{,_d}
echo " ******* ADDRESSES ********"
echo -n "i4 : IPv4 Listen Address = "; EvaluateVar ip4addr{,_d}
echo -n "i6 : IPv6 Listen Address = "; EvaluateVar ip6addr{,_d}
echo " ******* TRANSCODER ********"
echo " The only TC address supported is 'local' or the default 'none'"
echo -n "tc : Transcoder Address = "; EvaluateVar tcaddress{,_d}
if [ ! -z ${tcaddress+x} ]; then
echo -n "tm : Transcoder Modules = "; EvaluateVar tcmodules{,_d}
echo "ag : AMBE Audio Gain Sub-Menu"
if [ ! -z ${dstar_in_gain+x} ]; then
echo -n " D-Star Input gain = "; EvaluateVar dstar_in_gain{,_d}
fi
if [ ! -z ${dstar_out_gain+x} ]; then
echo -n " D-Star Output gain = "; EvaluateVar dstar_out_gain{,_d}
fi
if [ ! -z ${dmr_in_gain+x} ]; then
echo -n " DMR Input gain = "; EvaluateVar dmr_in_gain{,_d}
fi
if [ ! -z ${dmr_out_gain+x} ]; then
echo -n " DMR Output gain = "; EvaluateVar dmr_out_gain{,_d}
fi
fi
echo " ******* DMR Database ********"
echo -n "ds : Use RLX Server = "; EvaluateVar dmrdbuseserver{,_d}
echo -n "dt : Refresh time (in min) = "; EvaluateVar dmrdbrefresh{,_d}
echo -n "dp : Database path = "; EvaluateVar dmrdbpath{,_d}
echo " ******* SYSTEM FUSION ********"
echo -n "ye : YSF Autolink Enable = "; EvaluateVar ysfautolink{,_d}
if [ ! -z ${ysfautolink+x} ]; then
if [[ "$ysfautolink" == true ]]; then
echo -n "ym : YSF Autolink Module = "; EvaluateVar ysfmodule{,_d}
fi
fi
echo -n "yl : YSF Local Database = "; EvaluateVar ysflocaldb{,_d}
if [[ "$ysflocaldb" == true ]]; then
echo -n "yd : YSF Database Name = "; EvaluateVar ysfdbname{,_d}
echo -n "yu : YSF Database User = "; EvaluateVar ysfdbuser{,_d}
echo -n "yp : YSF Database Password = "; EvaluateVar ysfdbpw{,_d}
fi
echo " ******* YSFReflector Registry *******"
echo -n "rn : Registry Name = "; EvaluateVar ysfrname{,_d}
echo -n "rd : Registry Description = "; EvaluateVar ysfrdesc{,_d}
echo " ******* DEBUGGING ********"
echo -n "db : Debugging Support = "; EvaluateVar dbsupport{,_d}
echo
if [[ "$callsign" == URF* ]]; then
echo "w : Write configuration files (overwrites any existing files) and quit"
fi
echo "q : Quit without saving"
echo "u : Unset the value of <key> (revert to the default value)."
echo
read -p "Please input <key> <value> - omit value to toggle a true/false : " key value
if [[ "$key" == cs* && ${value^^} == URF* ]]; then
callsign="${value^^}"
callsign="${callsign:0:6}"
ysfrname_d="${callsign}"
unset tcaddress tcmodules ysf{autolink,module,localdb,dbname,dbuser,dbpw}
elif [[ "$key" == am* ]]; then CheckModules "${value^^}"
elif [[ "$key" == i4* ]]; then ip4addr="$value"
elif [[ "$key" == i6* ]]; then ip6addr="$value"
elif [[ "$key" == tc* ]]; then tcaddress="local"
elif [[ "$key" == tm* ]]; then CheckTranscodedModules "${value^^}"
elif [[ "$key" == ag* ]]; then
AudioGainMenu
key=x
elif [[ "$key" == ds* ]]; then SetBooleanValue dmrdbuseserver "$value"
elif [[ "$key" == dt* ]]; then dmrdbrefresh="$value"
elif [[ "$key" == dp* ]]; then dmrdbpath="$value"
elif [[ "$key" == ye* ]]; then SetBooleanValue ysfautolink "$value"
elif [[ "$key" == ym* ]]; then
ysfmodule="${value^^}"
ysfmodule="${ysfmodule:0:1}"
elif [[ "$key" == g3* ]]; then SetBooleanValue g3support "$value"
elif [[ "$key" == db* ]]; then SetBooleanValue dbsupport "$value"
elif [[ "$key" == yl* ]]; then SetBooleanValue ysflocaldb "$value"
elif [[ "$key" == yd* ]]; then ysfdbname="$value"
elif [[ "$key" == yu* ]]; then ysfdbuser="$value"
elif [[ "$key" == yp* ]]; then ysfdbpw="$value"
elif [[ "$key" == rn* ]]; then ysfrname="${value:0:16}"
elif [[ "$key" == rd* ]]; then ysfrdesc="${value:0:14}"
elif [[ "$key" == w* ]]; then
WriteCFGFiles
ListCFGFiles
exit 0
elif [[ "$key" == u* ]]; then
if [[ "$value" == cs* ]]; then unset callsign
elif [[ "$value" == am* ]]; then unset modules
elif [[ "$value" == i4* ]]; then unset ip4addr
elif [[ "$value" == i6* ]]; then unset ip6addr
elif [[ "$value" == tc* ]]; then unset tcaddress
elif [[ "$value" == tm* ]]; then tcmodules=${modules:0:1}
elif [[ "$value" == ds* ]]; then unset dmrdbuseserver
elif [[ "$value" == dt* ]]; then unset dmrdbrefresh
elif [[ "$value" == dp* ]]; then unset dmrdbpath
elif [[ "$value" == ye* ]]; then unset ysfautolink ysfmodule
elif [[ "$value" == ym* ]]; then unset ysfmodule
elif [[ "$value" == g3* ]]; then unset g3support
elif [[ "$value" == db* ]]; then unset dbsupport
elif [[ "$value" == yl* ]]; then unset ysflocaldb ysfdbname ysfdbuser ysfdbpw
elif [[ "$value" == yd* ]]; then unset ysfdbname
elif [[ "$value" == yu* ]]; then unset ysfdbuser
elif [[ "$value" == yp* ]]; then unset ysfdbpw
elif [[ "$value" == rn* ]]; then unset ysfrname
elif [[ "$value" == rd* ]]; then unset ysfrdesc
fi
fi
done
exit 0

@ -95,22 +95,3 @@ void CClient::WriteXml(std::ofstream &xmlFile)
} }
xmlFile << "</NODE>" << std::endl; xmlFile << "</NODE>" << std::endl;
} }
void CClient::GetJsonObject(char *Buffer)
{
char sz[512];
char mbstr[100];
char cs[16];
if (std::strftime(mbstr, sizeof(mbstr), "%A %c", std::localtime(&m_LastHeardTime)))
{
m_Callsign.GetCallsignString(cs);
::sprintf(sz, "{\"callsign\":\"%s\",\"module\":\"%c\",\"linkedto\":\"%c\",\"time\":\"%s\"}",
cs,
m_Callsign.GetCSModule(),
m_ReflectorModule,
mbstr);
::strcat(Buffer, sz);
}
}

@ -72,7 +72,6 @@ public:
// reporting // reporting
virtual void WriteXml(std::ofstream &); virtual void WriteXml(std::ofstream &);
virtual void GetJsonObject(char *);
protected: protected:
// data // data

@ -56,6 +56,7 @@
#define JIPV4EXTERNAL "IPv4External" #define JIPV4EXTERNAL "IPv4External"
#define JIPV6BINDING "IPv6Binding" #define JIPV6BINDING "IPv6Binding"
#define JIPV6EXTERNAL "IPv6External" #define JIPV6EXTERNAL "IPv6External"
#define JJSONPATH "JsonPath"
#define JM17 "M17" #define JM17 "M17"
#define JMMDVM "MMDVM" #define JMMDVM "MMDVM"
#define JMODE "Mode" #define JMODE "Mode"
@ -73,7 +74,6 @@
#define JREGISTRATIONID "RegistrationID" #define JREGISTRATIONID "RegistrationID"
#define JREGISTRATIONNAME "RegistrationName" #define JREGISTRATIONNAME "RegistrationName"
#define JSPONSOR "Sponsor" #define JSPONSOR "Sponsor"
#define JREFLSTATEPATH "ReflStatePath"
#define JSYSOPEMAIL "SysopEmail" #define JSYSOPEMAIL "SysopEmail"
#define JTRANSCODED "Transcoded" #define JTRANSCODED "Transcoded"
#define JTRANSCODER "Transcoder" #define JTRANSCODER "Transcoder"
@ -81,6 +81,7 @@
#define JURL "URL" #define JURL "URL"
#define JUSRP "USRP" #define JUSRP "USRP"
#define JWHITELISTPATH "WhitelistPath" #define JWHITELISTPATH "WhitelistPath"
#define JXMLPATH "XmlPath"
#define JYSF "YSF" #define JYSF "YSF"
#define JYSFTXRXDB "YSF TX/RX DB" #define JYSFTXRXDB "YSF TX/RX DB"
@ -463,8 +464,10 @@ bool CConfigure::ReadData(const std::string &path)
case ESection::files: case ESection::files:
if (0 == key.compare(JPIDPATH)) if (0 == key.compare(JPIDPATH))
data[g_Keys.files.pid] = value; data[g_Keys.files.pid] = value;
else if (0 == key.compare(JREFLSTATEPATH)) else if (0 == key.compare(JXMLPATH))
data[g_Keys.files.state] = value; data[g_Keys.files.xml] = value;
else if (0 == key.compare(JJSONPATH))
data[g_Keys.files.json] = value;
else if (0 == key.compare(JWHITELISTPATH)) else if (0 == key.compare(JWHITELISTPATH))
data[g_Keys.files.white] = value; data[g_Keys.files.white] = value;
else if (0 == key.compare(JBLACKLISTPATH)) else if (0 == key.compare(JBLACKLISTPATH))
@ -729,7 +732,7 @@ bool CConfigure::ReadData(const std::string &path)
// Other files // Other files
isDefined(ErrorLevel::fatal, JFILES, JPIDPATH, g_Keys.files.pid, rval); isDefined(ErrorLevel::fatal, JFILES, JPIDPATH, g_Keys.files.pid, rval);
isDefined(ErrorLevel::fatal, JFILES, JREFLSTATEPATH, g_Keys.files.state, rval); isDefined(ErrorLevel::fatal, JFILES, JXMLPATH, g_Keys.files.xml, rval);
if (isDefined(ErrorLevel::fatal, JFILES, JWHITELISTPATH, g_Keys.files.white, rval)) if (isDefined(ErrorLevel::fatal, JFILES, JWHITELISTPATH, g_Keys.files.white, rval))
checkFile(JFILES, JWHITELISTPATH, data[g_Keys.files.white]); checkFile(JFILES, JWHITELISTPATH, data[g_Keys.files.white]);
if (isDefined(ErrorLevel::fatal, JFILES, JBLACKLISTPATH, g_Keys.files.black, rval)) if (isDefined(ErrorLevel::fatal, JFILES, JBLACKLISTPATH, g_Keys.files.black, rval))

@ -66,6 +66,6 @@ struct SJsonKeys {
nxdniddb { "nxdnIdDbUrl", "nxdnIdDbMode", "nxdnIdDbRefresh", "nxdnIdDbFilePath" }, nxdniddb { "nxdnIdDbUrl", "nxdnIdDbMode", "nxdnIdDbRefresh", "nxdnIdDbFilePath" },
ysftxrxdb { "ysfIdDbUrl", "ysfIdDbMode", "ysfIdDbRefresh", "ysfIdDbFilePath" }; ysftxrxdb { "ysfIdDbUrl", "ysfIdDbMode", "ysfIdDbRefresh", "ysfIdDbFilePath" };
struct FILES { const std::string pid, state, white, black, interlink, terminal; } struct FILES { const std::string pid, xml, json, white, black, interlink, terminal; }
files { "pidFilePath", "stateFilePath", "whitelistFilePath", "blacklistFilePath", "interlinkFilePath", "g3TerminalFilePath" }; files { "pidFilePath", "xmlFilePath", "jsonFilePath", "whitelistFilePath", "blacklistFilePath", "interlinkFilePath", "g3TerminalFilePath" };
}; };

@ -24,7 +24,7 @@ INICHECK = inicheck
DBUTIL = dbutil DBUTIL = dbutil
include configure.mk include urfd.mk
ifeq ($(debug), true) ifeq ($(debug), true)
CFLAGS = -ggdb3 -DDEBUG -W -Werror -std=c++11 -MMD -MD CFLAGS = -ggdb3 -DDEBUG -W -Werror -std=c++11 -MMD -MD

@ -124,21 +124,3 @@ void CPeer::WriteXml(std::ofstream &xmlFile)
} }
xmlFile << "</PEER>" << std::endl; xmlFile << "</PEER>" << std::endl;
} }
void CPeer::GetJsonObject(char *Buffer)
{
char sz[512];
char mbstr[100];
char cs[16];
if (std::strftime(mbstr, sizeof(mbstr), "%A %c", std::localtime(&m_LastHeardTime)))
{
m_Callsign.GetCallsignString(cs);
::sprintf(sz, "{\"callsign\":\"%s\",\"linkedto\":\"%s\",\"time\":\"%s\"}",
cs,
m_ReflectorModules,
mbstr);
::strcat(Buffer, sz);
}
}

@ -68,7 +68,6 @@ public:
// reporting // reporting
virtual void WriteXml(std::ofstream &); virtual void WriteXml(std::ofstream &);
virtual void GetJsonObject(char *);
protected: protected:
// data // data

@ -298,7 +298,7 @@ void CReflector::RouterThread(const char ThisModule)
void CReflector::XmlReportThread() void CReflector::XmlReportThread()
{ {
const std::string path(g_Conf.GetString(g_Keys.files.state)); const std::string path(g_Conf.GetString(g_Keys.files.xml));
while (keep_running) while (keep_running)
{ {
// report to xml file // report to xml file

@ -76,24 +76,3 @@ void CUser::WriteXml(std::ofstream &xmlFile)
} }
xmlFile << "</STATION>" << std::endl; xmlFile << "</STATION>" << std::endl;
} }
void CUser::GetJsonObject(char *Buffer)
{
char sz[512];
char mbstr[100];
char my[16];
char rpt1[16];
if (std::strftime(mbstr, sizeof(mbstr), "%A %c", std::localtime(&m_LastHeardTime)))
{
m_My.GetCallsignString(my);
m_Rpt1.GetCallsignString(rpt1);
::sprintf(sz, "{\"callsign\":\"%s\",\"node\":\"%s\",\"module\":\"%c\",\"time\":\"%s\"}",
my,
rpt1,
m_Rpt1.GetCSModule(),
mbstr);
::strcat(Buffer, sz);
}
}

@ -41,7 +41,6 @@ public:
// reporting // reporting
void WriteXml(std::ofstream &); void WriteXml(std::ofstream &);
void GetJsonObject(char *);
protected: protected:
// data // data

6757
wiresx/bootstrap.css vendored

File diff suppressed because it is too large Load Diff

@ -1,73 +0,0 @@
<?php
// Initialize the session
session_start();
// Check if the user is logged in, otherwise redirect to login page
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
header("location: login.php");
exit;
}
// Include config file
require_once "configure.php";
// find current frequencies and initialize
$sql = "SELECT txfreq, rxfreq FROM ysfnodes WHERE callsign = ?";
if ($stmt = mysqli_prepare($link, $sql)) {
// Bind variables to the prepared statement as parameters
mysqli_stmt_bind_param($stmt, "s", $_SESSION["callsign"]);
// Attempt to execute the prepared statement
if (mysqli_stmt_execute($stmt)) {
// Store result
mysqli_stmt_store_result($stmt);
// Check if callsign exists, if yes then verify password
if (mysqli_stmt_num_rows($stmt) == 1) {
// Bind result variables
mysqli_stmt_bind_result($stmt, $tx_freq_hz, $rx_freq_hz);
if (mysqli_stmt_fetch($stmt)) {
$txfreq = $tx_freq_hz / 1000000.0;
$rxfreq = $rx_freq_hz / 1000000.0;
} else {
die("Can't bind frequencies\n");
}
} else {
die("Couldn't find one row for callsign\n");
}
} else {
die("Trouble SELECTing row\n");
}
mysqli_stmt_close($stmt);
} else {
die("Couldn't prepare SELECT statement\n");
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Set Frequency</title>
<link rel="stylesheet" href="bootstrap.css">
<style type="text/css">
body{ font: 14px sans-serif; text-align: center; }
</style>
</head>
<body>
<div class="page-header">
<h1>Summary for <?php echo htmlspecialchars($_SESSION["callsign"]); ?></h1>
</div>
<div class="wrapper">
<h2>Hot-Spot Frequencies Summary</h2>
<p>Hot-spot Callsign: <?php echo htmlspecialchars($_SESSION["callsign"]); ?></p>
<p>Transmit Frequency: <?php echo $txfreq; ?> MHz</p>
<p>Receive Frequency: <?php echo $rxfreq; ?> MHz</p>
<p>It can take up to 15 minutes for the frequency data to be available to the reflector. Please be patient.</p>
</div>
<p>
<a href="frequency.php" class="btn btn-primary">Reset Frequencies</a>
<a href="reset-password.php" class="btn btn-warning">Reset Your Password</a>
<a href="logout.php" class="btn btn-danger">Sign Out of Your Account</a>
</p>
</body>
</html>

@ -1,126 +0,0 @@
<?php
// Initialize the session
session_start();
// Check if the user is logged in, otherwise redirect to login page
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
header("location: login.php");
exit;
}
// Include config file
require_once "configure.php";
// find current frequencies and initialize
$sql = "SELECT txfreq, rxfreq FROM ysfnodes WHERE callsign = ?";
if ($stmt = mysqli_prepare($link, $sql)) {
// Bind variables to the prepared statement as parameters
mysqli_stmt_bind_param($stmt, "s", $_SESSION["callsign"]);
// Attempt to execute the prepared statement
if (mysqli_stmt_execute($stmt)) {
// Store result
mysqli_stmt_store_result($stmt);
// Check if callsign exists, if yes then verify password
if (mysqli_stmt_num_rows($stmt) == 1) {
// Bind result variables
mysqli_stmt_bind_result($stmt, $tx_freq_hz, $rx_freq_hz);
if (mysqli_stmt_fetch($stmt)) {
$txfreq = $tx_freq_hz / 1000000.0;
$rxfreq = $rx_freq_hz / 1000000.0;
} else {
die("Can't bind frequencies\n");
}
} else {
die("Couldn't find one row for callsign\n");
}
} else {
die("Trouble SELECTing row\n");
}
mysqli_stmt_close($stmt);
} else {
die("Couldn't prepare SELECT statement\n");
}
// Processing form data when form is submitted
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// Validate new password
if ($txfreq > 1000.0 || $txfreq < 10.0) {
$txfreq_err = "TX out of range.";
}
// Validate confirm password
if ($rxfreq > 1000.0 || $rxfreq < 10.0) {
$rxfreq_err = "RX out of range.";
}
// Check input errors before updating the database
if (empty($txfreq_err) && empty($rxfreq_err)) {
// Prepare an update statement
$sql = "UPDATE ysfnodes SET txfreq = ?, rxfreq = ? WHERE callsign = ?";
if($stmt = mysqli_prepare($link, $sql)){
// Bind variables to the prepared statement as parameters
$tx_freq_hz = $_POST["txfreq"] * 1000000;
$rx_freq_hz = $_POST["rxfreq"] * 1000000;
mysqli_stmt_bind_param($stmt, "iis", $tx_freq_hz, $rx_freq_hz, $_SESSION["callsign"]);
// Attempt to execute the prepared statement
if(mysqli_stmt_execute($stmt)){
// Frequencies updated successfully, go to summary
header("location: finish.php");
exit();
} else{
echo "Oops! Something went wrong. Please try again later.";
}
// Close statement
mysqli_stmt_close($stmt);
}
}
// Close connection
mysqli_close($link);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Set Frequency</title>
<link rel="stylesheet" href="bootstrap.css">
<style type="text/css">
body{ width: 350px; padding: 20px; }
</style>
</head>
<body>
<div class="page-header">
<h1>Set Frequencies for <?php echo htmlspecialchars($_SESSION["callsign"]); ?></h1>
</div>
<div class="wrapper">
<h2>Hot-Spot Frequencies</h2>
<p>Set your hot-spot frequencies (in MHz) here.</p>
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
<div class="form-group <?php echo (!empty($new_txfreq_err)) ? 'has-error' : ''; ?>">
<label>Tx Frequency (MHz)</label>
<input type="number" size="12" name="txfreq" min="10" max="1000" step="0.0005" class="form-control" value="<?php echo $txfreq; ?>">
<span class="help-block"><?php echo $txfreq_err; ?></span>
</div>
<div class="form-group <?php echo (!empty($rxfreq_err)) ? 'has-error' : ''; ?>">
<label>Rx Frequency (MHz)</label>
<input type="number" size="12" name="rxfreq" min="10" max="1000" step="0.0005" class="form-control" value="<?php echo $rxfreq; ?>">
<span class="help-block"><?php echo $rxfreq_err; ?></span>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Submit">
<a class="btn btn-link" href="finish.php">Cancel</a>
</div>
</form>
</div>
<p>
<a href="reset-password.php" class="btn btn-warning">Reset Your Password</a>
<a href="logout.php" class="btn btn-danger">Sign Out of Your Account</a>
</p>
</body>
</html>

@ -1,123 +0,0 @@
<?php
// Initialize the session
session_start();
// Check if the user is already logged in, if yes then redirect him to frequency page
if(isset($_SESSION["loggedin"]) && $_SESSION["loggedin"] === true){
header("location: frequency.php");
exit;
}
// Include config file
require_once "configure.php";
// Define variables and initialize with empty values
$callsign = $password = "";
$callsign_err = $password_err = "";
// Processing form data when form is submitted
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// Check if callsign is empty
if (empty(trim($_POST["callsign"]))) {
$callsign_err = "Please enter your callsign.";
} else {
$callsign = strtoupper(trim($_POST["callsign"]));
}
// Check if password is empty
if (empty(trim($_POST["password"]))) {
$password_err = "Please enter your password.";
} else {
$password = trim($_POST["password"]);
}
// Validate credentials
if (empty($callsign_err) && empty($password_err)) {
// Prepare a select statement
$sql = "SELECT callsign, password FROM ysfnodes WHERE callsign = ?";
if ($stmt = mysqli_prepare($link, $sql)) {
// Bind variables to the prepared statement as parameters
mysqli_stmt_bind_param($stmt, "s", $param_callsign);
// Set parameters
$param_callsign = $callsign;
// Attempt to execute the prepared statement
if (mysqli_stmt_execute($stmt)) {
// Store result
mysqli_stmt_store_result($stmt);
// Check if callsign exists, if yes then verify password
if (mysqli_stmt_num_rows($stmt) == 1) {
// Bind result variables
mysqli_stmt_bind_result($stmt, $callsign, $hashed_password);
if (mysqli_stmt_fetch($stmt)) {
if (password_verify($password, $hashed_password)) {
// Password is correct, so start a new session
session_start();
// Store data in session variables
$_SESSION["loggedin"] = true;
$_SESSION["callsign"] = $callsign;
// Redirect user to frequency page
header("location: frequency.php");
} else {
// Display an error message if password is not valid
$password_err = "The password you entered was not valid.";
}
}
} else {
// Display an error message if callsign doesn't exist
$callsign_err = "No account found with that callsign.";
}
} else {
echo "Oops! Something went wrong. Please try again later.";
}
// Close statement
mysqli_stmt_close($stmt);
}
}
// Close connection
mysqli_close($link);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
<link rel="stylesheet" href="bootstrap.css">
<style type="text/css">
body{ font: 14px sans-serif; }
.wrapper{ width: 350px; padding: 20px; }
</style>
</head>
<body>
<div class="wrapper">
<h2>Login</h2>
<p>Login with your hot-spot callsign.</p>
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
<div class="form-group <?php echo (!empty($callsign_err)) ? 'has-error' : ''; ?>">
<label>Username</label>
<input type="text" name="callsign" class="form-control" value="<?php echo $callsign; ?>">
<span class="help-block"><?php echo $callsign_err; ?></span>
</div>
<div class="form-group <?php echo (!empty($password_err)) ? 'has-error' : ''; ?>">
<label>Password</label>
<input type="password" name="password" class="form-control">
<span class="help-block"><?php echo $password_err; ?></span>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Login">
</div>
<p>Don't have an account? <a href="register.php">Sign up now</a>.</p>
</form>
</div>
</body>
</html>

@ -1,14 +0,0 @@
<?php
// Initialize the session
session_start();
// Unset all of the session variables
$_SESSION = array();
// Destroy the session.
session_destroy();
// Redirect to login page
header("location: login.php");
exit;
?>

@ -1,148 +0,0 @@
<?php
// Include config file
require_once "configure.php";
function IsValidCallsign(string $callsign)
{
$regex = '/^(([1-9][A-Z])|([A-PR-Z][0-9])|([A-PR-Z][A-Z][0-9]))[0-9A-Z]*[A-Z]$/';
$rval = preg_match($regex, $callsign);
if (FALSE === $rval)
die("trouble with callsign regular expression\n");
return $rval;
}
// Define variables and initialize with empty values
$callsign = $password = $confirm_password = "";
$callsign_err = $password_err = $confirm_password_err = "";
// Processing form data when form is submitted
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// Validate callsign
if (empty(trim($_POST["callsign"]))) {
$callsign_err = "Please enter your callsign.";
} else if (strlen(trim($_POST["callsign"])) > 7) {
$callsign_err = "Callsign is too long.";
} else if (! IsValidCallsign(strtoupper(trim($_POST["callsign"])))) {
$callsign_err = "Not a valid callsign.";
} else {
// Prepare a select statement
$sql = "SELECT * FROM ysfnodes WHERE callsign = ?";
if ($stmt = mysqli_prepare($link, $sql)) {
// Bind variables to the prepared statement as parameters
mysqli_stmt_bind_param($stmt, "s", $param_callsign);
// Set parameters
$param_callsign = strtoupper(trim($_POST["callsign"]));
// Attempt to execute the prepared statement
if (mysqli_stmt_execute($stmt)) {
/* store result */
mysqli_stmt_store_result($stmt);
if (mysqli_stmt_num_rows($stmt) == 1) {
$callsign_err = "This callsign is already taken.";
} else {
$callsign = strtoupper(trim($_POST["callsign"]));
}
} else {
echo "Oops! Something went wrong. Please try again later.";
}
// Close statement
mysqli_stmt_close($stmt);
}
}
// Validate password
if (empty(trim($_POST["password"]))) {
$password_err = "Please enter a password.";
} elseif (strlen(trim($_POST["password"])) < 6) {
$password_err = "Password must have atleast 6 characters.";
} else {
$password = trim($_POST["password"]);
}
// Validate confirm password
if (empty(trim($_POST["confirm_password"]))) {
$confirm_password_err = "Please confirm password.";
} else {
$confirm_password = trim($_POST["confirm_password"]);
if(empty($password_err) && ($password != $confirm_password)){
$confirm_password_err = "Password did not match.";
}
}
// Check input errors before inserting in database
if (empty($callsign_err) && empty($password_err) && empty($confirm_password_err)) {
// Prepare an insert statement
$sql = "INSERT INTO ysfnodes (callsign, password) VALUES (?, ?)";
if ($stmt = mysqli_prepare($link, $sql)) {
// Bind variables to the prepared statement as parameters
mysqli_stmt_bind_param($stmt, "ss", $param_callsign, $param_password);
// Set parameters
$param_callsign = $callsign;
$param_password = password_hash($password, PASSWORD_DEFAULT); // Creates a password hash
// Attempt to execute the prepared statement
if (mysqli_stmt_execute($stmt)) {
// Redirect to login page
header("location: login.php");
} else {
echo "Something went wrong. Please try again later.";
}
// Close statement
mysqli_stmt_close($stmt);
}
}
// Close connection
mysqli_close($link);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hot-Spot Frequency for WiresX Registration</title>
<link rel="stylesheet" href="bootstrap.css">
<style type="text/css">
body{ font: 14px sans-serif; }
.wrapper{ width: 350px; padding: 20px; }
</style>
</head>
<body>
<div class="wrapper">
<h2>Sign Up</h2>
<p>Please fill this form to create an account. Use your hot-spot callsign to register.</p>
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
<div class="form-group <?php echo (!empty($callsign_err)) ? 'has-error' : ''; ?>">
<label>Your Callsign</label>
<input type="text" name="callsign" class="form-control" value="<?php echo $callsign; ?>">
<span class="help-block"><?php echo $callsign_err; ?></span>
</div>
<div class="form-group <?php echo (!empty($password_err)) ? 'has-error' : ''; ?>">
<label>Password</label>
<input type="password" name="password" class="form-control" value="<?php echo $password; ?>">
<span class="help-block"><?php echo $password_err; ?></span>
</div>
<div class="form-group <?php echo (!empty($confirm_password_err)) ? 'has-error' : ''; ?>">
<label>Confirm Password</label>
<input type="password" name="confirm_password" class="form-control" value="<?php echo $confirm_password; ?>">
<span class="help-block"><?php echo $confirm_password_err; ?></span>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Submit">
<input type="reset" class="btn btn-default" value="Reset">
</div>
<p>Already have an account? <a href="login.php">Login here</a>.</p>
</form>
</div>
</body>
</html>

@ -1,106 +0,0 @@
<?php
// Initialize the session
session_start();
// Check if the user is logged in, otherwise redirect to login page
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
header("location: login.php");
exit;
}
// Include config file
require_once "configure.php";
// Define variables and initialize with empty values
$new_password = $confirm_password = "";
$new_password_err = $confirm_password_err = "";
// Processing form data when form is submitted
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// Validate new password
if (empty(trim($_POST["new_password"]))) {
$new_password_err = "Please enter the new password.";
} elseif (strlen(trim($_POST["new_password"])) < 6) {
$new_password_err = "Password must have atleast 6 characters.";
} else{
$new_password = trim($_POST["new_password"]);
}
// Validate confirm password
if (empty(trim($_POST["confirm_password"]))) {
$confirm_password_err = "Please confirm the password.";
} else {
$confirm_password = trim($_POST["confirm_password"]);
if (empty($new_password_err) && ($new_password != $confirm_password)) {
$confirm_password_err = "Password did not match.";
}
}
// Check input errors before updating the database
if (empty($new_password_err) && empty($confirm_password_err)) {
// Prepare an update statement
$sql = "UPDATE ysfnodes SET password = ? WHERE callsign = ?";
if($stmt = mysqli_prepare($link, $sql)){
// Bind variables to the prepared statement as parameters
mysqli_stmt_bind_param($stmt, "ss", $param_password, $param_callsign);
// Set parameters
$param_password = password_hash($new_password, PASSWORD_DEFAULT);
$param_callsign = $_SESSION["callsign"];
// Attempt to execute the prepared statement
if(mysqli_stmt_execute($stmt)){
// Password updated successfully. Destroy the session, and redirect to login page
session_destroy();
header("location: login.php");
exit();
} else{
echo "Oops! Something went wrong. Please try again later.";
}
// Close statement
mysqli_stmt_close($stmt);
}
}
// Close connection
mysqli_close($link);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Reset Password</title>
<link rel="stylesheet" href="bootstrap.css">
<style type="text/css">
body{ font: 14px sans-serif; }
.wrapper{ width: 350px; padding: 20px; }
</style>
</head>
<body>
<div class="wrapper">
<h2>Reset Password</h2>
<p>Please fill out this form to reset your password.</p>
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
<div class="form-group <?php echo (!empty($new_password_err)) ? 'has-error' : ''; ?>">
<label>New Password</label>
<input type="password" name="new_password" class="form-control" value="<?php echo $new_password; ?>">
<span class="help-block"><?php echo $new_password_err; ?></span>
</div>
<div class="form-group <?php echo (!empty($confirm_password_err)) ? 'has-error' : ''; ?>">
<label>Confirm Password</label>
<input type="password" name="confirm_password" class="form-control">
<span class="help-block"><?php echo $confirm_password_err; ?></span>
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Submit">
<a class="btn btn-link" href="finish.php">Cancel</a>
</div>
</form>
</div>
</body>
</html>
Loading…
Cancel
Save

Powered by TurnKey Linux.