From e7c84ddf59101d650562ef1cfc4bec64ccae6dd2 Mon Sep 17 00:00:00 2001 From: Greg Thornton Date: Tue, 22 Dec 2020 10:13:50 -0600 Subject: [PATCH] Network config web ui and fallback wifi hotspot (#1655) --- firmware_mod/config/hostapd.conf.dist | 14 ++ firmware_mod/config/udhcpd.conf.dist | 6 + firmware_mod/config/wifi.conf.dist | 2 + firmware_mod/run.sh | 45 +--- firmware_mod/scripts/common_functions.sh | 46 ++++ firmware_mod/scripts/wifi.sh | 293 +++++++++++++++++++++++ firmware_mod/scripts/wpa_action.sh | 12 + firmware_mod/www/cgi-bin/ui_system.cgi | 52 +++- firmware_mod/www/js/system.js | 21 +- firmware_mod/www/system.html | 35 ++- 10 files changed, 480 insertions(+), 46 deletions(-) create mode 100644 firmware_mod/config/hostapd.conf.dist create mode 100644 firmware_mod/config/udhcpd.conf.dist create mode 100644 firmware_mod/config/wifi.conf.dist create mode 100755 firmware_mod/scripts/wifi.sh create mode 100755 firmware_mod/scripts/wpa_action.sh diff --git a/firmware_mod/config/hostapd.conf.dist b/firmware_mod/config/hostapd.conf.dist new file mode 100644 index 0000000..dfe8552 --- /dev/null +++ b/firmware_mod/config/hostapd.conf.dist @@ -0,0 +1,14 @@ +interface=wlan0 +ctrl_interface=/var/run/hostapd +channel=6 +driver=nl80211 +beacon_int=100 +hw_mode=g +wme_enabled=1 +wpa_key_mgmt=WPA-PSK +wpa_pairwise=CCMP +max_num_sta=8 +wpa_group_rekey=86400 +ssid=dafang +wpa=2 +wpa_passphrase=ismart12 diff --git a/firmware_mod/config/udhcpd.conf.dist b/firmware_mod/config/udhcpd.conf.dist new file mode 100644 index 0000000..30727a1 --- /dev/null +++ b/firmware_mod/config/udhcpd.conf.dist @@ -0,0 +1,6 @@ +start 10.0.0.2 +end 10.0.0.10 +interface wlan0 +lease_file /tmp/udhcpd.leases +option router 10.0.0.1 +option subnet 255.0.0.0 diff --git a/firmware_mod/config/wifi.conf.dist b/firmware_mod/config/wifi.conf.dist new file mode 100644 index 0000000..54dab23 --- /dev/null +++ b/firmware_mod/config/wifi.conf.dist @@ -0,0 +1,2 @@ +connect_timeout=60 +scan_interval=30 diff --git a/firmware_mod/run.sh b/firmware_mod/run.sh index 21e0bc4..c48c608 100755 --- a/firmware_mod/run.sh +++ b/firmware_mod/run.sh @@ -121,13 +121,18 @@ if [ -f $CONFIGPATH/usb_eth_driver.conf ]; then insmod /system/sdcard/driver/usbnet.ko insmod /system/sdcard/driver/asix.ko - network_interface_name="eth0" + ## Configure network address + if [ -f "$CONFIGPATH/staticip.conf" ]; then + configure_static_net_iface eth0 >> $LOGPATH + else + # Configure with DHCP client + ifconfig eth0 up + udhcpc_status=$(udhcpc -i eth0 -p /var/run/udhcpc.eth0.pid -b -x hostname:"$(hostname)") + echo "udhcpc: $udhcpc_status" >> $LOGPATH + fi else ## Start Wifi: - if [ ! -f $CONFIGPATH/wpa_supplicant.conf ]; then - echo "Warning: You have to configure wpa_supplicant in order to use wifi. Please see /system/sdcard/config/wpa_supplicant.conf.dist for further instructions." - fi - MAC=$(grep MAC < /params/config/.product_config | cut -c16-27 | sed 's/\(..\)/\1:/g;s/:$//') + MAC=$(get_wifi_mac) if [ -f /driver/8189es.ko ]; then # Its a DaFang insmod /driver/8189es.ko rtw_initmac="$MAC" @@ -138,35 +143,7 @@ else # Its a Wyzecam V2 insmod /driver/rtl8189ftv.ko rtw_initmac="$MAC" fi - wpa_supplicant_status="$(wpa_supplicant -d -B -i wlan0 -c $CONFIGPATH/wpa_supplicant.conf -P /var/run/wpa_supplicant.pid)" - echo "wpa_supplicant: $wpa_supplicant_status" >> $LOGPATH - - network_interface_name="wlan0" -fi - -## Configure network address -if [ -f "$CONFIGPATH/staticip.conf" ]; then - # Install a resolv.conf if present so DNS can work - if [ -f "$CONFIGPATH/resolv.conf" ]; then - cp "$CONFIGPATH/resolv.conf" /etc/resolv.conf - fi - - # Configure staticip/netmask from config/staticip.conf - staticip_and_netmask=$(cat "$CONFIGPATH/staticip.conf" | grep -v "^$" | grep -v "^#") - ifconfig "$network_interface_name" $staticip_and_netmask - ifconfig "$network_interface_name" up - # Configure default gateway - if [ -f "$CONFIGPATH/defaultgw.conf" ]; then - defaultgw=$(cat "$CONFIGPATH/defaultgw.conf" | grep -v "^$" | grep -v "^#") - route add default gw $defaultgw $network_interface_name - echo "Configured $defaultgw as default gateway" >> $LOGPATH - fi - echo "Configured $network_interface_name with static address $staticip_and_netmask" >> $LOGPATH -else - # Configure with DHCP client - ifconfig "$network_interface_name" up - udhcpc_status=$(udhcpc -i "$network_interface_name" -p /var/run/udhcpc.pid -b -x hostname:"$(hostname)") - echo "udhcpc: $udhcpc_status" >> $LOGPATH + /system/sdcard/scripts/wifi.sh start >> $LOGPATH fi ## Set Timezone diff --git a/firmware_mod/scripts/common_functions.sh b/firmware_mod/scripts/common_functions.sh index 6511d4d..208dc25 100755 --- a/firmware_mod/scripts/common_functions.sh +++ b/firmware_mod/scripts/common_functions.sh @@ -832,3 +832,49 @@ getFonts() { echo -n ">`/system/sdcard/bin/busybox basename $i` " done } + +get_wifi_mac() { + grep MAC < /params/config/.product_config | cut -c16-27 | sed 's/\(..\)/\1:/g;s/:$//' +} + +configure_static_net_iface() { + local network_interface_name="$1" + local CONFIGPATH=/system/sdcard/config + + # Install a resolv.conf if present so DNS can work + if [ -f "$CONFIGPATH/resolv.conf" ]; then + cp "$CONFIGPATH/resolv.conf" /etc/resolv.conf + fi + + # Configure staticip/netmask from config/staticip.conf + local staticip_and_netmask=$(cat "$CONFIGPATH/staticip.conf" | grep -v "^$" | grep -v "^#") + ifconfig "$network_interface_name" $staticip_and_netmask + ifconfig "$network_interface_name" up + # Configure default gateway + if [ -f "$CONFIGPATH/defaultgw.conf" ]; then + local defaultgw=$(cat "$CONFIGPATH/defaultgw.conf" | grep -v "^$" | grep -v "^#") + route add default gw $defaultgw $network_interface_name + echo "Configured $defaultgw as default gateway" + fi + echo "Configured $network_interface_name with static address $staticip_and_netmask" +} + +wpa_config_set() { + local wpa_config=/system/sdcard/config/wpa_supplicant.conf + local key="$1" + local val="$2" + if [ ! -s "$wpa_config" ]; then cp "$wpa_config.dist" "$wpa_config"; fi + if grep -q "^[[:space:]]*$key=" "$wpa_config"; then + sed -i "s/^[[:space:]]*$key=.*\$/$key=$val/" "$wpa_config" + else + sed -i "/}/i $key=$val" "$wpa_config" + fi +} + +wpa_config_get() { + local wpa_config=/system/sdcard/config/wpa_supplicant.conf + local key="$1" + if [ -s "$wpa_config" ]; then + grep "^[[:space:]]*$key=" "$wpa_config" | cut -d "=" -f2 + fi +} diff --git a/firmware_mod/scripts/wifi.sh b/firmware_mod/scripts/wifi.sh new file mode 100755 index 0000000..58ff0e0 --- /dev/null +++ b/firmware_mod/scripts/wifi.sh @@ -0,0 +1,293 @@ +#!/bin/sh + +# Included for configure_static_net_iface and get_wifi_mac +. /system/sdcard/scripts/common_functions.sh + +CONFIGPATH=/system/sdcard/config +BBOX_BIN=/system/sdcard/bin/busybox + +WIFI_IFACE=wlan0 +WIFI_LOGFILE=/system/sdcard/log/wifi.log +WIFI_BIN=/system/sdcard/scripts/wifi.sh +WIFI_CONFIG="$CONFIGPATH/wifi.conf" +WIFI_VERBOSE=1 + +MIN_CONNECT_TIMEOUT=30 +MIN_SCAN_INTERVAL=10 + +WPA_BIN=/system/bin/wpa_supplicant +WPA_CONFIG="$CONFIGPATH/wpa_supplicant.conf" +WPA_PIDFILE=/var/run/wpa_supplicant.pid + +WPA_CLI_BIN=/system/bin/wpa_cli +WPA_CLI_PIDFILE=/var/run/wpa_cli.pid + +WPA_ACTION_BIN=/system/sdcard/scripts/wpa_action.sh +WPA_ACTION_PIDFILE=/var/run/wpa_action.pid + +HAP_BIN=/system/bin/hostapd +HAP_CONFIG="$CONFIGPATH/hostapd.conf" +HAP_PIDFILE=/var/run/hostapd.pid + +UDHCPC_BIN=/sbin/udhcpc +UDHCPC_PIDFILE="/var/run/udhcpc.$WIFI_IFACE.pid" + +UDHCPD_BIN=/sbin/udhcpd +UDHCPD_CONFIG="$CONFIGPATH/udhcpd.conf" +UDHCPD_PIDFILE=/var/run/udhcpd.pid + +AP_SCANNER_PIDFILE=/var/run/ap_scanner.pid + +log() { + local LEVEL="$1" + if [ -z "$WIFI_VERBOSE" ] && [ "$LEVEL" = "verbose" ]; then return 0; fi + shift + echo "$(date +"%D %T")" "$@" | "$BBOX_BIN" tee -a "$WIFI_LOGFILE" +} + +kill_wait() { + local PIDFILE="$1" + local PID=$(cat "$PIDFILE") + log verbose "Killing pid $PID ($PIDFILE)" + kill "$PID" + log verbose "Waiting pid $PID ($PIDFILE)" + wait "$PID" + log verbose "Removing pidfile $PIDFILE" + rm -f "$PIDFILE" +} + +run_bg() { + local PIDFILE="$1" + shift + "$BBOX_BIN" nohup "$@" 2>&1 >/dev/null & + echo "$!" > "$PIDFILE" +} + +reset_iface() { + log info "Resetting interface" + ifconfig "$WIFI_IFACE" down + ifconfig "$WIFI_IFACE" 0.0.0.0 + ifconfig -a +} + +wpa_supplicant_start() { + log info "Starting wpa_supplicant" + "$WPA_BIN" -d -B -P "$WPA_PIDFILE" -i "$WIFI_IFACE" -c "$WPA_CONFIG" +} + +wpa_supplicant_stop() { + log info "Stopping wpa_supplicant" + kill_wait "$WPA_PIDFILE" +} + +hostapd_init() { + if [ -s "$HAP_CONFIG" ]; then return 0; fi + sed "s/ssid=\(.*\$\)/ssid=\1-$(get_wifi_mac)/" "$HAP_CONFIG.dist" > "$HAP_CONFIG" +} + +hostapd_start() { + log info "Starting hostapd" + "$HAP_BIN" -d -B -P "$HAP_PIDFILE" "$HAP_CONFIG" +} + +hostapd_stop() { + log info "Stopping hostapd" + kill_wait "$HAP_PIDFILE" +} + +udhcpc_start() { + log info "Starting udhcpc" + "$UDHCPC_BIN" -b -p "$UDHCPC_PIDFILE" -i "$WIFI_IFACE" -x hostname:"$(hostname)" +} + +udhcpc_stop() { + log info "Stopping udhcpc" + kill_wait "$UDHCPC_PIDFILE" +} + +udhcpd_init() { + if [ ! -s "$UDHCPD_CONFIG" ]; then + cp "$UDHCPD_CONFIG.dist" "$UDHCPD_CONFIG" + fi +} + +udhcpd_start() { + log info "Starting udhcpd" + "$UDHCPD_BIN" "$UDHCPD_CONFIG" +} + +udhcpd_stop() { + log info "Stopping udhcpd" + kill_wait "$UDHCPD_PIDFILE" +} + +wpa_cli_start() { + log info "Starting wpa_cli" + "$WPA_CLI_BIN" -B -P "$WPA_CLI_PIDFILE" -i "$WIFI_IFACE" -a "$WPA_ACTION_BIN" +} + +wpa_cli_stop() { + log info "Stopping wpa_cli" + kill_wait "$WPA_CLI_PIDFILE" +} + +wpa_action_connected() { + log info "Connected" + wpa_action_watchdog_stop +} + +wpa_action_disconnected() { + log info "Disconnected" + wpa_action_watchdog_stop + wpa_action_watchdog_start +} + +wpa_action_watchdog() { + local TIMEOUT="$(get_config "$WIFI_CONFIG" connect_timeout)" + if [ -z "$TIMEOUT" ] || [ "$TIMEOUT" -lt "$MIN_CONNECT_TIMEOUT" ]; then + TIMEOUT="$MIN_CONNECT_TIMEOUT" + fi + log verbose "wpa_action watchdog sleeping $TIMEOUT seconds" + sleep "$TIMEOUT" + rm -f "$WPA_ACTION_PIDFILE" + log verbose "wpa_action watchdog switching to ap mode" + exec "$WIFI_BIN" ap +} + +wpa_action_watchdog_start() { + log info "Starting wpa_action watchdog" + run_bg "$WPA_ACTION_PIDFILE" "$WIFI_BIN" wpa_action watchdog +} + +wpa_action_watchdog_stop() { + log info "Stopping wpa_action watchdog" + kill_wait "$WPA_ACTION_PIDFILE" +} + +ap_scanner_ssid() { + grep -v '^[[:space:]]*#' "$WPA_CONFIG" | grep "ssid=" | head -1 | cut -d "=" -f2 +} + +ap_scanner_scan() { + iwlist "$WIFI_IFACE" scanning | grep '^[[:space:]]*ESSID:' | grep -v '""' | cut -d ":" -f2 +} + +ap_scanner() { + while true; do + local INTERVAL="$(get_config "$WIFI_CONFIG" scan_interval)" + if [ -z "$INTERVAL" ] || [ "$INTERVAL" -lt "$MIN_SCAN_INTERVAL" ]; then + INTERVAL="$MIN_SCAN_INTERVAL" + fi + log verbose "ap_scanner sleeping $INTERVAL seconds" + sleep "$INTERVAL" + local SSID="$(ap_scanner_ssid)" + if [ -z "$SSID" ] && [ "$SSID" != '""' ]; then + log verbose "ap_scanner has no ssid, skipping scan" + continue + fi + local SCAN="$(ap_scanner_scan)" + local SCAN_COUNT="$(echo "$SCAN" | wc -l)" + log verbose "ap_scanner found $SCAN_COUNT ssids" + if [ -z "$SCAN" ]; then continue; fi + if echo "$SCAN" | grep -q "$SSID"; then + rm -f "$AP_SCANNER_PIDFILE" + log verbose "ap_scanner found ssid, switching to station mode" + exec "$WIFI_BIN" station + else + log verbose "ap_scanner found no configured ssid" + fi + done +} + +ap_scanner_start() { + log info "Starting ap_scanner" + run_bg "$AP_SCANNER_PIDFILE" "$WIFI_BIN" ap_scanner +} + +ap_scanner_stop() { + log info "Stopping ap_scanner" + kill_wait "$AP_SCANNER_PIDFILE" +} + +station_ifup() { + if [ -s "$CONFIGPATH/staticip.conf" ]; then + configure_static_net_iface "$WIFI_IFACE" + else + ifconfig "$WIFI_IFACE" up + udhcpc_start + fi +} + +station_start() { + log info "Starting station mode" + ap_stop + wpa_supplicant_start + wpa_action_watchdog_start + wpa_cli_start + station_ifup +} + +station_stop() { + log info "Stopping station mode" + wpa_action_watchdog_stop + wpa_cli_stop + udhcpc_stop + wpa_supplicant_stop + reset_iface +} + +ap_ifup() { + local IP="$(grep router "$UDHCPD_CONFIG" | cut -d ' ' -f3)" + local NETMASK="$(grep subnet "$UDHCPD_CONFIG" | cut -d ' ' -f3)" + ifconfig "$WIFI_IFACE" up "$IP" netmask "$NETMASK" +} + +ap_start() { + log info "Starting access point mode" + station_stop + hostapd_start + ap_ifup + udhcpd_start + ap_scanner_start +} + +ap_stop() { + log info "Stopping access point mode" + ap_scanner_stop + udhcpd_stop + hostapd_stop + reset_iface +} + +wifi_init() { + hostapd_init + udhcpd_init + if [ ! -s "$WIFI_CONFIG" ]; then + cp "$WIFI_CONFIG.dist" "$WIFI_CONFIG" + fi +} + +wifi_start() { + log info "Starting wifi" + wifi_init + if [ -s "$WPA_CONFIG" ]; then + station_start + else + ap_start + fi +} + +case $1 in + start) + wifi_start + ;; + ap|station) + "$1_start" + ;; + wpa_action) + "$1_$2" + ;; + ap_scanner) + "$1" + ;; +esac diff --git a/firmware_mod/scripts/wpa_action.sh b/firmware_mod/scripts/wpa_action.sh new file mode 100755 index 0000000..59d3f82 --- /dev/null +++ b/firmware_mod/scripts/wpa_action.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +WIFI_BIN=/system/sdcard/scripts/wifi.sh + +case "$2" in + CONNECTED) + "$WIFI_BIN" wpa_action connected + ;; + DISCONNECTED) + "$WIFI_BIN" wpa_action disconnected + ;; +esac diff --git a/firmware_mod/www/cgi-bin/ui_system.cgi b/firmware_mod/www/cgi-bin/ui_system.cgi index dff58db..47ccb02 100755 --- a/firmware_mod/www/cgi-bin/ui_system.cgi +++ b/firmware_mod/www/cgi-bin/ui_system.cgi @@ -20,6 +20,11 @@ if [ -n "$F_cmd" ]; then echo "timezone#:#$(/system/sdcard/bin/busybox awk -F '\t' -v tzn="$(cat /system/sdcard/config/timezone.conf)" '{print ""}' /system/sdcard/www/json/timezones.tsv | tr -d '\n')" echo "currenttime#:#Current time is $(date) - $(cat /etc/TZ)" echo "github_token#:#$(get_config /system/sdcard/config/updates.conf github_token)" + echo "wifi_ssid#:#$(wpa_config_get ssid | sed 's/^"\([^"]*\)"$/\1/')" + echo "connect_timeout#:#$(get_config /system/sdcard/config/wifi.conf connect_timeout)" + echo "scan_interval#:#$(get_config /system/sdcard/config/wifi.conf scan_interval)" + echo "ap_ssid#:#$(get_config /system/sdcard/config/hostapd.conf ssid)" + echo "usb_eth#:#$([ -f /system/sdcard/config/usb_eth_driver.conf ] && echo on || echo off)" ;; save_config) if [ -n ${F_hostname} ]; then @@ -29,7 +34,7 @@ if [ -n "$F_cmd" ]; then echo "$hst" > /system/sdcard/config/hostname.conf if hostname "$hst"; then echo "Success

" - else + else echo "Failed

" fi fi @@ -62,6 +67,50 @@ if [ -n "$F_cmd" ]; then rewrite_config /system/sdcard/config/updates.conf github_token "$github_token" fi fi + if [ -n ${F_wifi_ssid} ]; then + F_wifi_ssid=$(echo "$F_wifi_ssid" | sed 's/+/ /g') + wifi_ssid=$(printf '%b' "${F_wifi_ssid//%/\\x}") + echo "

Setting wifi SSID to: $wifi_ssid

" + wpa_config_set ssid "\"$wifi_ssid\"" + fi + if [ -n ${F_wifi_password} ]; then + wifi_password=$(printf '%b' "${F_wifi_password//%/\\x}") + echo "

Setting wifi password to: $wifi_password

" + wpa_config_set psk "\"$wifi_password\"" + fi + if [ -n ${F_connect_timeout} ]; then + F_connect_timeout=$(echo "$F_connect_timeout" | sed 's/+/ /g') + connect_timeout=$(printf '%b' "${F_connect_timeout//%/\\x}") + echo "

Setting wifi connect timeout to: $connect_timeout

" + rewrite_config /system/sdcard/config/wifi.conf connect_timeout "$connect_timeout" + fi + if [ -n ${F_scan_interval} ]; then + F_scan_interval=$(echo "$F_scan_interval" | sed 's/+/ /g') + scan_interval=$(printf '%b' "${F_scan_interval//%/\\x}") + echo "

Setting access point scan interval to: $scan_interval

" + rewrite_config /system/sdcard/config/wifi.conf scan_interval "$scan_interval" + fi + if [ -n ${F_ap_ssid} ]; then + F_ap_ssid=$(echo "$F_ap_ssid" | sed 's/+/ /g') + ap_ssid=$(printf '%b' "${F_ap_ssid//%/\\x}") + echo "

Setting access point SSID to: $ap_ssid

" + rewrite_config /system/sdcard/config/hostapd.conf ssid "$ap_ssid" + fi + if [ -n ${F_ap_password} ]; then + ap_password=$(printf '%b' "${F_ap_password//%/\\x}") + echo "

Setting access point password to: $ap_password

" + rewrite_config /system/sdcard/config/hostapd.conf wpa_passphrase "$ap_password" + fi + if [ -n ${F_usb_eth} ]; then + usb_eth=$(printf '%b' "${F_usb_eth//%/\\x}") + if [ "$usb_eth" = "on" ]; then + echo "

Enabling USB ethernet

" + touch /system/sdcard/config/usb_eth_driver.conf + else + echo "

Disabling USB ethernet

" + rm -f /system/sdcard/config/usb_eth_driver.conf + fi + fi return ;; *) @@ -72,4 +121,3 @@ if [ -n "$F_cmd" ]; then fi exit 0 - diff --git a/firmware_mod/www/js/system.js b/firmware_mod/www/js/system.js index 5ab3b0b..72ec754 100644 --- a/firmware_mod/www/js/system.js +++ b/firmware_mod/www/js/system.js @@ -2,14 +2,21 @@ function saveConfig() { //Open modal window document.getElementById('save_confirm').style.display='block' - + var postData = { cmd: "save_config", hostname: $('#hostname').val(), password: $('#password').val(), timezone: $('#timezone').children("option:selected").val(), github_token: $('#github_token').val(), + wifi_ssid: $('#wifi_ssid').val(), + wifi_password: $('#wifi_password').val(), + ap_ssid: $('#ap_ssid').val(), + ap_password: $('#ap_password').val(), + connect_timeout: $('#connect_timeout').val(), + scan_interval: $('#scan_interval').val(), + usb_eth: $('#usb_eth').is(":checked") ? 'on' : 'off', ntp: $('#ntp').val()}; - + $.post("cgi-bin/ui_system.cgi",postData,function(result){ //Open modal window if ( result != "") @@ -24,15 +31,17 @@ function saveConfig() { //Function get config function getConfig() { // get config and put to hmtl elements - $.get("cgi-bin/ui_system.cgi", {cmd: "get_config"}, function(config){ + $.get("cgi-bin/ui_system.cgi", {cmd: "get_config"}, function(config){ var config_all = config.split("\n"); for (var i = 0; i < config_all.length-1; i++) { - var config_info = config_all[i].split("#:#"); + var config_info = config_all[i].split("#:#"); if ( config_info[0] == "timezone" || config_info[0] == "currenttime") $('#'+config_info[0]).html(config_info[1]); + else if (config_info[0] === "usb_eth") + $('#'+config_info[0]).prop('checked', config_info[1] === 'on'); else $('#'+config_info[0]).attr("value",config_info[1]); - + } }); @@ -46,4 +55,4 @@ function onLoad() { } //Main program -onLoad(); \ No newline at end of file +onLoad(); diff --git a/firmware_mod/www/system.html b/firmware_mod/www/system.html index ce2d757..0681860 100755 --- a/firmware_mod/www/system.html +++ b/firmware_mod/www/system.html @@ -4,13 +4,16 @@

System configuration

- + - + - + + + +
@@ -39,6 +42,30 @@ + +

@@ -58,4 +85,4 @@ - \ No newline at end of file +