diff --git a/scripts/openwrt/Makefile b/scripts/openwrt/Makefile new file mode 100644 index 0000000..a8ae354 --- /dev/null +++ b/scripts/openwrt/Makefile @@ -0,0 +1,66 @@ +# +# Copyright (C) 2026 3proxy.org +# +# This is free software, licensed under the BSD 3-Clause License. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=3proxy +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 + +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz +# A trailing ? tells the download helper the URL is complete and PKG_SOURCE +# must not be appended to it. +PKG_SOURCE_URL:=https://codeload.github.com/3proxy/3proxy/tar.gz/refs/tags/$(PKG_VERSION)? +PKG_HASH:=35b07de1046f3aaeac4a7085101b7e5c453efa3527cbdc42a84690366c7ecfa8 + +PKG_MAINTAINER:=Vladimir Dubrovin +PKG_LICENSE:=BSD-3-Clause +PKG_LICENSE_FILES:=copying +PKG_CPE_ID:=cpe:/a:3proxy:3proxy + +PKG_BUILD_PARALLEL:=1 + +include $(INCLUDE_DIR)/package.mk + +define Package/3proxy + SECTION:=net + CATEGORY:=Network + SUBMENU:=Web Servers/Proxies + TITLE:=tiny free proxy server + URL:=https://3proxy.org/ + DEPENDS:=+libopenssl +libpcre2 +endef + +define Package/3proxy/description + 3proxy is a tiny free proxy server supporting HTTP, HTTPS, FTP, SOCKS v4/v4a/v5, + POP3, SMTP, IMAP, TCP and UDP port mapping, with access control, bandwidth + limiting and traffic accounting. +endef + +define Package/3proxy/conffiles +/etc/config/3proxy +endef + +# Makefile.Linux appends to CFLAGS and LDFLAGS internally; the target flags have +# to be added rather than substituted, or the defines it relies on are lost. +define Build/Compile + $(MAKE) -C $(PKG_BUILD_DIR) -f Makefile.Linux \ + CC="$(TARGET_CC)" \ + EXTRA_CFLAGS="$(TARGET_CFLAGS) $(TARGET_CPPFLAGS)" \ + EXTRA_LDFLAGS="$(TARGET_LDFLAGS)" \ + PLUGINS= +endef + +define Package/3proxy/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/bin/3proxy $(1)/usr/bin/3proxy + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/3proxy.config $(1)/etc/config/3proxy + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/3proxy.init $(1)/etc/init.d/3proxy +endef + +$(eval $(call BuildPackage,3proxy)) diff --git a/scripts/openwrt/files/3proxy.config b/scripts/openwrt/files/3proxy.config new file mode 100644 index 0000000..f8bf232 --- /dev/null +++ b/scripts/openwrt/files/3proxy.config @@ -0,0 +1,59 @@ +config 3proxy 'global' + option enabled '0' + option nscache '65536' +# option nscache6 '65536' + # static records, added to the cache; 0.0.0.0 blackholes a name +# list nsrecord 'ads.example.com 0.0.0.0' + option maxconn '128' + option auth 'iponly' + option log 'syslog' +# option timeouts '1 5 30 60 180 1800 15 60 15 5 5' +# list include '/etc/3proxy/extra.cfg' + list nserver '8.8.8.8' + list nserver '8.8.4.4' +# list user 'admin:CL:password' +# list extra_config 'timeouts 1 5 30 60 180 1800 15 60' + # access list used by services which do not define their own + list acl 'lan' + +# Access rules are named sections referenced by services. The order of the +# references decides precedence: 3proxy stops at the first rule that matches. +config acl 'lan' + option action 'allow' + option src '192.168.1.0/24' + +config acl 'deny_private' + option action 'deny' + option dst '10.0.0.0/8,172.16.0.0/12,192.168.0.0/16' + +#config acl 'via_upstream' +# option action 'allow' +# list parent 'upstream' + +# Parent proxies extend an allow rule to build a chain. Weights group them: +# parents whose weights sum to 1000 form one group and one is picked at random, +# several groups are chained in order. +#config parent 'upstream' +# option weight '1000' +# option type 'socks5' +# option ip '10.0.0.1' +# option port '1080' +# option username '' +# option password '' + +config service 'proxy' + option enabled '0' + option type 'proxy' + option port '3128' +# option bind '' +# option external '' +# option extra '' +# option auth 'strong' + list acl 'deny_private' + list acl 'lan' + +config service 'socks' + option enabled '0' + option type 'socks' + option port '1080' + list acl 'lan' diff --git a/scripts/openwrt/files/3proxy.init b/scripts/openwrt/files/3proxy.init new file mode 100644 index 0000000..b7a91c1 --- /dev/null +++ b/scripts/openwrt/files/3proxy.init @@ -0,0 +1,460 @@ +#!/bin/sh /etc/rc.common + +START=50 +USE_PROCD=1 + +CFGFILE=/var/etc/3proxy.cfg +PROG=/usr/bin/3proxy + +# 3proxy.cfg is order dependent: authentication and access rules apply to the +# service lines that follow them, and the access list has to be flushed before +# each service. The file is written as one global block followed by one block +# per service. + +acl_written=0 + +append_line() { + echo "$1" >> "$CFGFILE" +} + +append_include() { + echo "include $1" >> "$CFGFILE" +} + +# The limiter directives carry their own ACL pattern rather than attaching to a +# preceding allow rule, and 3proxy defaults every omitted field to *, so the +# trailing wildcards are dropped again to keep the file readable. +# logformat takes a single argument, so a format containing spaces has to be +# quoted. Quotes already present in the UCI value are not doubled. +append_logformat() { + local fmt="$1" + + case "$fmt" in + '"'*'"') ;; + *) fmt="\"$fmt\"" ;; + esac + + echo "logformat $fmt" >> "$CFGFILE" +} + +limit_match() { + local users src dst ports ops weekdays periods out + + config_get users "$1" users '*' + config_get src "$1" src '*' + config_get dst "$1" dst '*' + config_get ports "$1" ports '*' + config_get ops "$1" operations '*' + config_get weekdays "$1" weekdays '*' + config_get periods "$1" timeperiods '*' + + out="$users $src $dst $ports $ops $weekdays $periods" + while [ "${out% \*}" != "$out" ]; do out="${out% \*}"; done + + echo "$out" +} + +append_limit() { + local type rate period number count_type limit match + + config_get type "$1" type + + case "$type" in + bandlimin|bandlimout|nobandlimin|nobandlimout|\ + connlim|noconnlim|\ + countin|countout|countall|nocountin|nocountout|nocountall) ;; + *) + echo "3proxy: limit '$1' has unknown type '$type', ignored" >&2 + return 0 + ;; + esac + + match=$(limit_match "$1") + + case "$type" in + bandlimin|bandlimout) + config_get rate "$1" rate + [ -n "$rate" ] || { + echo "3proxy: limit '$1' needs a rate, ignored" >&2 + return 0 + } + echo "$type $rate $match" >> "$CFGFILE" + ;; + connlim) + config_get rate "$1" rate + config_get period "$1" period 0 + [ -n "$rate" ] || { + echo "3proxy: limit '$1' needs a rate, ignored" >&2 + return 0 + } + echo "$type $rate $period $match" >> "$CFGFILE" + ;; + countin|countout|countall) + config_get number "$1" number + config_get count_type "$1" count_type + config_get limit "$1" limit + [ -n "$number" ] && [ -n "$count_type" ] && [ -n "$limit" ] || { + echo "3proxy: limit '$1' needs number, count_type and limit, ignored" >&2 + return 0 + } + echo "$type $number $count_type $limit $match" >> "$CFGFILE" + ;; + *) + echo "$type $match" >> "$CFGFILE" + ;; + esac + + return 0 +} + +append_pcre_extend() { + echo "pcre_extend $1" >> "$CFGFILE" +} + +append_pcre() { + local match_type action regexp rewrite ace + + config_get match_type "$1" match_type + config_get action "$1" action + config_get regexp "$1" regexp + config_get rewrite "$1" rewrite + config_get ace "$1" ace + + [ -n "$match_type" ] && [ -n "$action" ] && [ -n "$regexp" ] || { + echo "3proxy: pcre '$1' needs match_type, action and regexp, ignored" >&2 + return 0 + } + + # Catch bad values here: 3proxy rejects the whole configuration on an + # unknown type or action, which would leave the router without a proxy. + case "$action" in + allow|deny|dunno) ;; + *) + echo "3proxy: pcre '$1' action '$action' is not allow, deny or dunno, ignored" >&2 + return 0 + ;; + esac + + local part + for part in $(echo "$match_type" | tr ',' ' '); do + case "$part" in + request|cliheader|srvheader|clidata|srvdata) ;; + *) + echo "3proxy: pcre '$1' match_type '$part' is unknown, ignored" >&2 + return 0 + ;; + esac + done + + if [ -n "$rewrite" ]; then + echo "pcre_rewrite $match_type $action $regexp $rewrite${ace:+ $ace}" >> "$CFGFILE" + else + echo "pcre $match_type $action $regexp${ace:+ $ace}" >> "$CFGFILE" + fi + + config_list_foreach "$1" extend append_pcre_extend + return 0 +} + +append_nsrecord() { + set -- $1 + if [ $# -ne 2 ]; then + echo "3proxy: nsrecord '$*' needs a hostname and an address, ignored" >&2 + return 0 + fi + + echo "nsrecord $1 $2" >> "$CFGFILE" + nsrecord_written=1 + return 0 +} + +append_nserver() { + echo "nserver $1" >> "$CFGFILE" +} + +append_user() { + users="$users $1" +} + +# $1 is the name of an acl section referenced by a service, or by the global +# section as the default access list. +append_acl() { + local action users src dst ports + + config_get action "$1" action allow + config_get users "$1" users + config_get src "$1" src + config_get dst "$1" dst + config_get ports "$1" ports + + case "$action" in + allow|deny) ;; + *) + echo "3proxy: acl '$1' has unknown action '$action', ignored" >&2 + return 0 + ;; + esac + + echo "$action ${users:-*} ${src:-*} ${dst:-*} ${ports:-*}" >> "$CFGFILE" + acl_written=1 + + if [ "$action" = "allow" ]; then + config_list_foreach "$1" parent append_parent + else + config_get _parent "$1" parent + [ -z "$_parent" ] || echo "3proxy: acl '$1' is a deny rule, its parents are ignored" >&2 + fi + + return 0 +} + +# $1 is the name of a parent section referenced by an acl. "parent" extends the +# allow rule that precedes it, so these are emitted directly after their rule. +append_parent() { + local weight type ip port username password line + + config_get weight "$1" weight 1000 + config_get type "$1" type + config_get ip "$1" ip + config_get port "$1" port + config_get username "$1" username + config_get password "$1" password + + [ -n "$type" ] && [ -n "$ip" ] && [ -n "$port" ] || { + echo "3proxy: parent '$1' needs type, ip and port, ignored" >&2 + return 0 + } + + line="parent $weight $type $ip $port" + if [ -n "$username" ]; then + line="$line $username" + [ -n "$password" ] && line="$line $password" + fi + + echo "$line" >> "$CFGFILE" + return 0 +} + +# TLS parameters that take a value. The UCI option name is the directive name. +SSL_VALUE_OPTIONS="ssl_server_cert ssl_server_key ssl_client_cert ssl_client_key + ssl_client_ciphersuites ssl_server_ciphersuites + ssl_client_cipher_list ssl_server_cipher_list + ssl_client_min_proto_version ssl_server_min_proto_version + ssl_client_max_proto_version ssl_server_max_proto_version + ssl_server_ca_file ssl_server_ca_key ssl_server_ca_dir ssl_server_ca_store + ssl_client_ca_file ssl_client_ca_dir ssl_client_ca_store + ssl_client_sni ssl_client_alpn ssl_client_mode ssl_certcache" + +# The TLS switches apply to every service below them, so they leak from one +# service to the next unless turned back off. These track what is currently in +# effect - all off, matching the defaults - so a directive is written only when +# a service actually needs a different state. +ssl_state_mitm=0 +ssl_state_server=0 +ssl_state_client=0 +ssl_state_client_verify=0 +ssl_state_server_verify=0 + +# $1 section, $2 uci option, $3 state variable, $4 directive on, $5 directive off +append_ssl_toggle() { + local want have + + config_get_bool want "$1" "$2" 0 + have=$(eval echo \$$3) + + [ "$want" = "$have" ] && return 0 + + if [ "$want" -gt 0 ]; then + echo "$4" >> "$CFGFILE" + else + echo "$5" >> "$CFGFILE" + fi + + eval "$3=$want" + return 0 +} + +append_ssl() { + local opt value mitm server cert key cverify + + for opt in $SSL_VALUE_OPTIONS; do + config_get value "$1" "$opt" + [ -n "$value" ] && echo "$opt $value" >> "$CFGFILE" + done + + append_ssl_toggle "$1" ssl_mitm ssl_state_mitm ssl_mitm ssl_nomitm + append_ssl_toggle "$1" ssl_server ssl_state_server ssl_serv ssl_noserv + append_ssl_toggle "$1" ssl_client ssl_state_client ssl_cli ssl_nocli + append_ssl_toggle "$1" ssl_client_verify ssl_state_client_verify \ + ssl_client_verify ssl_client_no_verify + append_ssl_toggle "$1" ssl_server_verify ssl_state_server_verify \ + ssl_server_verify ssl_server_no_verify + + config_get_bool mitm "$1" ssl_mitm 0 + config_get_bool cverify "$1" ssl_client_verify 0 + [ "$mitm" -gt 0 ] && [ "$cverify" -gt 0 ] || [ "$mitm" -eq 0 ] || \ + echo "3proxy: service '$1' spoofs certificates without ssl_client_verify, upstream certificates are not checked" >&2 + + config_get_bool server "$1" ssl_server 0 + if [ "$server" -gt 0 ]; then + config_get cert "$1" ssl_server_cert + config_get key "$1" ssl_server_key + [ -n "$cert" ] && [ -n "$key" ] || \ + echo "3proxy: service '$1' requires TLS from clients but has no ssl_server_cert/ssl_server_key" >&2 + fi + + return 0 +} + +append_service() { + local enabled type port bind external extra auth args + local bind_interface external_interface logformat + + config_get_bool enabled "$1" enabled 0 + [ "$enabled" -gt 0 ] || return 0 + + config_get type "$1" type + [ -n "$type" ] || { + echo "3proxy: service '$1' has no type, ignored" >&2 + return 0 + } + + config_get port "$1" port + config_get bind "$1" bind + config_get external "$1" external + config_get extra "$1" extra + config_get bind_interface "$1" bind_interface + config_get external_interface "$1" external_interface + config_get logformat "$1" logformat + config_get auth "$1" auth "$global_auth" + + echo "" >> "$CFGFILE" + echo "flush" >> "$CFGFILE" + [ -n "$auth" ] && echo "auth $auth" >> "$CFGFILE" + + # Rules referenced by the service, in the order they are listed. A service + # without its own list falls back to the global one. + acl_written=0 + config_list_foreach "$1" acl append_acl + [ "$acl_written" -gt 0 ] || config_list_foreach global acl append_acl + + [ -n "$logformat" ] && append_logformat "$logformat" + + append_ssl "$1" + + args="" + [ -n "$port" ] && args="$args -p$port" + [ -n "$bind" ] && args="$args -i$bind" + [ -n "$external" ] && args="$args -e$external" + [ -n "$bind_interface" ] && args="$args -Di$bind_interface" + [ -n "$external_interface" ] && args="$args -De$external_interface" + [ -n "$extra" ] && args="$args $extra" + + echo "$type$args" >> "$CFGFILE" + return 0 +} + +write_config() { + local nscache nscache6 maxconn log timeouts fakeresolve logformat + local authcache_type authcache_time authcache_size + local counter_file counter_type counter_name pcre_options + + mkdir -p "$(dirname "$CFGFILE")" + : > "$CFGFILE" + + config_get nscache global nscache + config_get nscache6 global nscache6 + config_get maxconn global maxconn + config_get global_auth global auth iponly + config_get log global log syslog + config_get timeouts global timeouts + config_get logformat global logformat + config_get_bool fakeresolve global fakeresolve 0 + config_get authcache_type global authcache_type + config_get authcache_time global authcache_time + config_get authcache_size global authcache_size + config_get counter_file global counter_file + config_get counter_type global counter_type + config_get counter_name global counter_name + config_get pcre_options global pcre_options + + config_list_foreach global nserver append_nserver + [ -n "$nscache" ] && echo "nscache $nscache" >> "$CFGFILE" + [ -n "$nscache6" ] && echo "nscache6 $nscache6" >> "$CFGFILE" + + # Static records are added to the cache, so they have to come after it. + nsrecord_written=0 + config_list_foreach global nsrecord append_nsrecord + [ "$nsrecord_written" -eq 0 ] || [ -n "$nscache$nscache6" ] || \ + echo "3proxy: nsrecord needs nscache or nscache6 to be set" >&2 + + case "$log" in + syslog) echo "log" >> "$CFGFILE" ;; + none|"") ;; + *) echo "log $log" >> "$CFGFILE" ;; + esac + + users="" + config_list_foreach global user append_user + [ -n "$users" ] && echo "users$users" >> "$CFGFILE" + + [ -n "$timeouts" ] && echo "timeouts $timeouts" >> "$CFGFILE" + [ "$fakeresolve" -gt 0 ] && echo "fakeresolve" >> "$CFGFILE" + [ -n "$logformat" ] && append_logformat "$logformat" + + if [ -n "$authcache_type" ]; then + [ -n "$authcache_time" ] || authcache_time=600 + echo "authcache $authcache_type $authcache_time${authcache_size:+ $authcache_size}" >> "$CFGFILE" + fi + + if [ -n "$counter_file" ]; then + echo "counter $counter_file${counter_type:+ $counter_type}${counter_name:+ $counter_name}" >> "$CFGFILE" + fi + + [ -n "$pcre_options" ] && echo "pcre_options $pcre_options" >> "$CFGFILE" + + # Both lists are order sensitive: 3proxy stops at the first match, so the + # exempting rules (nobandlimin and friends) have to be listed first. + config_list_foreach global pcre append_pcre + config_list_foreach global limit append_limit + + config_list_foreach global include append_include + config_list_foreach global extra_config append_line + + [ -n "$maxconn" ] && echo "maxconn $maxconn" >> "$CFGFILE" + + config_foreach append_service service + + return 0 +} + +start_service() { + local enabled + + config_load 3proxy + config_get_bool enabled global enabled 0 + + [ "$enabled" -gt 0 ] || { + echo "3proxy is disabled in /etc/config/3proxy" >&2 + return 1 + } + + write_config + + procd_open_instance + procd_set_param command "$PROG" "$CFGFILE" + procd_set_param file "$CFGFILE" + procd_set_param respawn + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_close_instance +} + +service_triggers() { + procd_add_reload_trigger "3proxy" +} + +reload_service() { + stop + start +}