4
\$\begingroup\$

This project is designed to enable clear-net browsing over the Tor network with the Lynx web-browser. Currently everything seems to be functioning as intended, however, it would be most appreciated if anyone could point out mistakes and/or ways that this project can be improved.


Questions

  • Are there any mistakes in this project that would harm the privacy of those using this project?

  • Any additional features needed?

  • Is there a way to add .onion Top Level Domain look-up support to Lynx browser?

Note, currently for .onion addresses result in the following error...

Looking up 127.0.0.1:9999
Making HTTP connection to 127.0.0.1:9999
Sending HTTP request.
HTTP request sent; waiting for response.
Alert!: HTTP/1.0 405 Method Not Allowed
Data transfer complete

lynx: Start file could not be found or is not text/html or text/plain
      Exiting...

... so while clear-net browsing is groovy, for some reason browsing hidden services be going a bit goofy.


Requirements

Prior to utilizing this project, the Lynx web browser should be installed, and bash --version should report a version number of 4 or greater.

Optional dependencies include firejail and socat, for process sandboxing and DNS over Tor respectively.


Setup

The ReadMe file contains more detailed instructions for setup, but the TLDR is...

  • Clone
mkdir -vp ~/git/hub/paranoid-linux
cd ~/git/hub/paranoid-linux

git clone --recurse-submodules [email protected]:paranoid-linux/torrific-lynx.git
  • Link/Install
cd torrific-lynx

sudo ./update-configs.sh

ln -s "${PWD}/torrific-lynx" "${HOME}/bin/"
  • Use
torrific-lynx 'https://duckduckgo.com'
  • Exit when done by pressing the q key to quit

Source Code

Note, source code for this question is maintained on GitHub at paranoid-linux/torrific-lynx, what is included here are the scripts, configuration file, and shared functions required to test/review without need of any Git fanciness.

torrific-lynx.cfg

include:/etc/lynx/lynx.cfg

cso_proxy:http://127.0.0.1:9999/
finger_proxy:http://127.0.0.1:9999/
ftp_proxy:http://127.0.0.1:9999/
gopher_proxy:http://127.0.0.1:9999/
https_proxy:http://127.0.0.1:9999/
http_proxy:http://127.0.0.1:9999/
newspost_proxy:http://127.0.0.1:9999/
newsreply_proxy:http://127.0.0.1:9999/
news_proxy:http://127.0.0.1:9999/
nntp_proxy:http://127.0.0.1:9999/
snewspost_proxy:http://127.0.0.1:9999/
snewsreply_proxy:http://127.0.0.1:9999/
snews_proxy:http://127.0.0.1:9999/
wais_proxy:http://127.0.0.1:9999/
no_proxy:http://127.0.0.1:9999/


FORCE_SSL_COOKIES_SECURE:TRUE
PERSISTENT_COOKIES:FALSE

torrific-lynx

#!/usr/bin/env bash


## Find true directory script resides in, true name, and true path
__SOURCE__="${BASH_SOURCE[0]}"
while [[ -h "${__SOURCE__}" ]]; do
    __SOURCE__="$(find "${__SOURCE__}" -type l -ls | sed -n 's@^.* -> \(.*\)@\1@p')"
done
__DIR__="$(cd -P "$(dirname "${__SOURCE__}")" && pwd)"
__NAME__="${__SOURCE__##*/}"
__PATH__="${__DIR__}/${__NAME__}"
__AUTHOR__='S0AndS0'
__DESCRIPTION__='Enables clear-net browsing over the Tor network via Lynx web browser'


_cfg_path="${__DIR__}/torrific-lynx.cfg"
_url='https://check.torproject.org/'
_useragent='Mozilla/5.0 (Windows NT 10.0; rv:68.0) Gecko/20100101 Firefox/68.0'
# _useragent='L_y_n_x/2.8.9dev.16 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/3.5.17'
_verbose=''
_disable_firejail=''
_disable_socat=''
_firejail_lynx_profile="/usr/local/etc/firejail/lynx.profile"
_firejail_socat_profile=""
_cloud_flare_hidden_service='dns4torpnlfs2ifuz2s2yf3fc7rdmsbhm6rw75euj35pac6ap25zgqad.onion'


## Provides: 'falure' <line-number> <command> exit-code
source "${__DIR__}/shared_functions/modules/trap-failure/failure.sh"
trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR

## Provides:  'argument_parser <ref_to_allowed_args> <ref_to_user_supplied_args>'
source "${__DIR__}/shared_functions/modules/argument-parser/argument-parser.sh"


__license__(){
    _year="$(date +'%Y')"
    cat <<EOF
${__DESCRIPTION__}
Copyright (C) ${_year:-2020} ${__AUTHOR__:-S0AndS0}

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, version 3 of the License.

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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
EOF
}


usage() {
    local _message="${1}"
    cat <<EOF
${__DESCRIPTION__}


--help      -h

    Prints this message and exists


--license   -l

    Prints license and exits


--cfg-path  --cfg='${_cfg_path}'

    Lynx configuration file path


--useragent='${_useragent}'

    User agent string to submit to remote servers


--verbose   -v

    Prints full Lynx command and options prior to running


--disable-firejail

    Disables Firejail customizations, note system defaults are **not** modified


--disable-socat

    Disables usage of Socat for DNS over Tor


--firejail-lynx-profile --lynx-profile="${_firejail_lynx_profile}"

    Firejail profile to use for Lynx sandbox


${_url}

    Web address to load at start-up


## Example usage


    ${__NAME__} 'https://check.torproject.org/'
EOF

    if (("${#_message}")); then
        printf >&2 '\n## Error: %s\n' "${_message}"
    fi
}


#
#    Read & save recognized arguments to variables
#
_args=("${@}")
_valid_args=('--help|-h|help:bool'
             '--license|-l|license:bool'
             '--verbose|-v:bool'
             '--disable-firejail:bool'
             '--disable-socat:bool'
             '--useragent:print'
             '--cfg-path|--cfg:path'
             '--firejail-lynx-profile|--lynx-profile:path'
             '--url:print-nil')
argument_parser '_args' '_valid_args'
_exit_status="$?"


((_help)) || ((_exit_status)) && {
    usage
    exit ${_exit_status:-0}
}

((_license)) && {
    __license__
    exit ${_exit_status:-0}
}


[[ -e "$(which lynx)" ]] || {
    usage 'Cannot find executable path for lynx'
    exit 1
}

[[ -f "${_cfg_path}" ]] || {
    usage "Cannot find configuration file for path -> ${_cfg_path}"
    exit 1
}


_lynx_opts=(
    -cfg="${_cfg_path}"
    -useragent="${_useragent}"
)


_firejail_opts=(
    --private
)

! ((_disable_firejail)) && ! ((_disable_socat)) && {
    _firejail_opts+=(
        --hosts-file="${__DIR__}/hosts"
        --dns='127.0.0.1'
    )
}

! ((_disable_firejail)) && [[ -f "${_firejail_lynx_profile}" ]] && {
   _firejail_opts+=(
      "--profile=${_firejail_lynx_profile}"
   )
} || {
   usage "No firejail profile found at -> ${_firejail_lynx_profile}"
   exit 1
}


_socat_opts=(
    'TCP4-LISTEN:443'
    'reuseaddr'
    "fork SOCKS4A:127.0.0.1:${_cloud_flare_hidden_service}:443"
    'socksport=9150'
)
_socat_opts=("$(printf '%s\n' "$(IFS=,; printf '%s' "${_socat_opts[*]}")")")


kill_socat() {
    local _socat_pid="${1:?No PID provided!}"
    ((_verbose)) && {
        printf 'Killing socat PID -> %i\n' "${_socat_pid}"
    }
    sudo kill "${_socat_pid}"
    local _ps_search="$(ps aux | grep "${_socat_pid}" | grep -v grep)"
    (("${#_ps_search}")) && {
        # printf >&2 'Trying to kill socat again PID -> %i\n' "${_socat_pid}"
        # sudo kill "${_socat_pid}"

        ## TODO: Find cleaner way of killing socat process

        printf >&2 'Trying to killall socat\n'
        sudo killall socat
    }
}


[[ -e "$(which firejail)" ]] && ! ((_disable_firejail)) && {
    _lynx_path="$(which -a lynx | head -2 | tail -1)"
    [[ -e "$(which socat)" ]] && ! ((_disable_socat)) && {
        ((_verbose)) && {
            printf '# DEBUG: sudo %s %s &\n' "$(which socat)" "${_socat_opts[*]}"
        }
        ## TODO: Find cleaner way of asking for elevated permissions
        sudo echo "Starting socat..."
        sudo "$(which socat)" ${_socat_opts[@]} &
        _socat_pid="$!"
        trap "kill_socat ${_socat_pid}" RETURN SIGINT SIGTERM EXIT
    }

    ((_verbose)) && {
        printf '# DEBUG: %s %s -- %s %s %s\n' "$(which firejail)" "${_firejail_opts[*]}" "${_lynx_path}" "${_lynx_opts[*]}" "${_url}"
    }
    "$(which firejail)" "${_firejail_opts[@]}" -- "${_lynx_path}" "${_lynx_opts[@]}" "${_url}"
} || {
    ((_verbose)) && {
       printf '# DEBUG: %s %s %s\n' "$(which lynx)" "${_lynx_opts[@]}" "${_url}"
    }
    "$(which lynx)" "${_lynx_opts[@]}" "${_url}"
}

shared_functions/modules/argument-parser/argument-parser.sh

#!/usr/bin/env bash


# argument-parser.sh, source it in other Bash scripts for argument parsing
# Copyright (C) 2019  S0AndS0
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation; version 3 of the License.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.


shopt -s extglob


_TRUE='1'
_DEFAULT_ACCEPTABLE_ARG_LIST=('--help|-h:bool' '--foo|-f:print' '--path:path-nil')


arg_scrubber_alpha_numeric(){ printf '%s' "${@//[^a-z0-9A-Z]/}"; }


arg_scrubber_regex(){ printf '%s' "$(sed 's@.@\\.@g' <<<"${@//[^[:print:]$'\t'$'\n']/}")"; }


arg_scrubber_list(){
    printf '%s' "$(sed 's@\.\.*@.@g; s@--*@-@g' <<<"${@//[^a-z0-9A-Z,+_./@:-]/}")"
}


arg_scrubber_path(){
    printf '%s' "$(sed 's@\.\.*@.@g; s@--*@-@g' <<<"${@//[^a-z0-9A-Z ~+_./@:-]/}")"
}


arg_scrubber_posix(){
    _value="${@//[^a-z0-9A-Z_.-]/}"
    _value="$(sed 's@^[-_.]@@g; s@[-_.]$@@g; s@\.\.*@.@g; s@--*@-@g' <<<"${_value}")"
    printf '%s' "${_value::32}"
}


return_scrubbed_arg(){
    _raw_value="${1}"
    _opt_type="${2:?## Error - no option type provided to return_scrubbed_arg}"
    case "${_opt_type}" in
        'bool'*)  _value="${_TRUE}"      ;;
        'raw'*)   _value="${_raw_value}" ;;
        'path'*)  _value="$(arg_scrubber_path "${_raw_value}")"  ;;
        'posix'*) _value="$(arg_scrubber_posix "${_raw_value}")" ;;
        'print'*) _value="${_raw_value//[^[:print:]]/}"          ;;
        'regex'*) _value="$(arg_scrubber_regex "${_raw_value}")" ;;
        'list'*)  _value="$(arg_scrubber_list "${_raw_value}")"  ;;
        'alpha_numeric'*) _value="$(arg_scrubber_alpha_numeric "${_raw_value}")" ;;
    esac

    if [[ "${_opt_type}" =~ ^'bool'* ]] || [[ "${_raw_value}" == "${_value}" ]]; then
        printf '%s' "${_value}"
    else
        printf '## Error - return_scrubbed_arg detected differences in values\n' >&2
        return 1
    fi
}


argument_parser(){
    local -n _arg_user_ref="${1:?# No reference to an argument list/array provided}"
    local -n _arg_accept_ref="${2:-_DEFAULT_ACCEPTABLE_ARG_LIST}"
    _args_user_list=("${_arg_user_ref[@]}")
    unset _assigned_args
    for _acceptable_args in ${_arg_accept_ref[@]}; do
        ## Take a break when user supplied argument list becomes empty
        [[ "${#_args_user_list[@]}" == '0' ]] && break
        ## First in listed acceptable arg is used as variable name to save value to
        ##  example, '--foo-bar fizz' would transmute into '_foo_bar=fizz'
        _opt_name="${_acceptable_args%%[:|]*}"
        _var_name="${_opt_name#*[-]}"
        _var_name="${_var_name#*[-]}"
        _var_name="_${_var_name//-/_}"
        ## Divine the type of argument allowed for this iteration of acceptable args
        case "${_acceptable_args}" in
            *':'*) _opt_type="${_acceptable_args##*[:]}" ;;
            *)     _opt_type="bool"                      ;;
        esac
        ## Set case expressions to match user arguments against and for non-bool type
        ##  what alternative case expression to match on.
        ##  example '--foo|-f' will also check for '--foo=*|-f=*'
        _arg_opt_list="${_acceptable_args%%:*}"
        _valid_opts_pattern="@(${_arg_opt_list})"
        case "${_arg_opt_list}" in
            *'|'*) _valid_opts_pattern_alt="@(${_arg_opt_list//|/=*|}=*)" ;;
            *)     _valid_opts_pattern_alt="@(${_arg_opt_list}=*)"        ;;
        esac
        ## Attempt to match up user supplied arguments with those that are valid
        for (( i = 0; i < "${#_args_user_list[@]}"; i++ )); do
            _user_opt="${_args_user_list[${i}]}"
            case "${_user_opt}" in
                ${_valid_opts_pattern})     ## Parse for script-name --foo bar or --true
                    if [[ "${_opt_type}" =~ ^'bool'* ]]; then
                        _var_value="$(return_scrubbed_arg "${_user_opt}" "${_opt_type}")"
                        _exit_status="${?}"
                    else
                        i+=1
                        _var_value="$(return_scrubbed_arg "${_args_user_list[${i}]}" "${_opt_type}")"
                        _exit_status="${?}"
                        unset _args_user_list[$(( i - 1 ))]
                    fi
                ;;
                ${_valid_opts_pattern_alt}) ## Parse for script-name --foo=bar
                    _var_value="$(return_scrubbed_arg "${_user_opt#*=}" "${_opt_type}")"
                    _exit_status="${?}"
                ;;
                *)                          ## Parse for script-name direct_value
                    case "${_opt_type}" in
                        *'nil'|*'none')
                            _var_value="$(return_scrubbed_arg "${_user_opt}" "${_opt_type}")"
                            _exit_status="${?}"
                        ;;
                    esac
                ;;
            esac
            if ((_exit_status)); then return ${_exit_status}; fi
            ## Break on matched options after clearing temp variables and re-assigning
            ##  list (array) of user supplied arguments.
            ## Note, re-assigning is to ensure the next looping indexes correctly
            ##  and is designed to require less work on each iteration
            if [ -n "${_var_value}" ]; then
                declare -g "${_var_name}=${_var_value}"
                declare -ag "_assigned_args+=('${_opt_name}=\"${_var_value}\"')"
                unset _user_opt
                unset _var_value
                unset _args_user_list[${i}]
                unset _exit_status
                _args_user_list=("${_args_user_list[@]}")
                break
            fi
        done
        unset _opt_type
        unset _opt_name
        unset _var_name
    done
}

Note, the source code for argument-parser.sh is a Git Submodule maintained on GitHub at bash-utilities/argument-parser, and can be cloned individually via...

mkdir -vp ~/git/hub/bash-utilities
cd ~/git/hub/bash-utilities

git clone [email protected]:bash-utilities/argument-parser.git

shared_functions/modules/trap-failure/failure.sh

#!/usr/bin/env bash


# Bash Trap Failure, a submodule for other Bash scripts tracked by Git
# Copyright (C) 2019  S0AndS0
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation; version 3 of the License.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.


## Outputs Front-Mater formatted failures for functions not returning 0
## Use the following line after sourcing this file to set failure trap
##    trap 'failure "LINENO" "BASH_LINENO" "${BASH_COMMAND}" "${?}"' ERR
failure(){
    local -n _lineno="${1:-LINENO}"
    local -n _bash_lineno="${2:-BASH_LINENO}"
    local _last_command="${3:-${BASH_COMMAND}}"
    local _code="${4:-0}"

    ## Workaround for read EOF combo tripping traps
    ((_code)) || {
      return "${_code}"
    }

    local _last_command_height="$(wc -l <<<"${_last_command}")"

    local -a _output_array=()
    _output_array+=(
        '---'
        "lines_history: [${_lineno} ${_bash_lineno[*]}]"
        "function_trace: [${FUNCNAME[*]}]"
        "exit_code: ${_code}"
    )

    [[ "${#BASH_SOURCE[@]}" -gt '1' ]] && {
        _output_array+=('source_trace:')
        for _item in "${BASH_SOURCE[@]}"; do
            _output_array+=("  - ${_item}")
        done
    } || {
        _output_array+=("source_trace: [${BASH_SOURCE[*]}]")
    }

    [[ "${_last_command_height}" -gt '1' ]] && {
        _output_array+=(
            'last_command: ->'
            "${_last_command}"
        )
    } || {
        _output_array+=("last_command: ${_last_command}")
    }

    _output_array+=('---')
    printf '%s\n' "${_output_array[@]}" >&2
    exit ${_code}
}

Note, the source code for failure.sh is a Git Submodule maintained on GitHub at bash-utilities/trap-failure, and can be cloned individually via...

mkdir -vp ~/git/hub/bash-utilities
cd ~/git/hub/bash-utilities

git clone [email protected]:bash-utilities/trap-failure.git
\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.