summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/squid/sg_acl.conf.tmpl18
-rw-r--r--data/templates/squid/squid.conf.tmpl115
-rw-r--r--data/templates/squid/squidGuard.conf.tmpl91
-rw-r--r--debian/control4
-rwxr-xr-xdebian/rules4
-rw-r--r--debian/vyos-1x.install1
-rw-r--r--debian/vyos-1x.postinst3
-rw-r--r--interface-definitions/include/webproxy-url-filtering.xml.i119
-rw-r--r--interface-definitions/service_webproxy.xml.in644
-rw-r--r--op-mode-definitions/webproxy.xml121
-rw-r--r--python/vyos/template.py7
-rw-r--r--python/vyos/util.py24
-rwxr-xr-xsmoketest/scripts/cli/test_service_webproxy.py7
-rwxr-xr-xsrc/completion/list_webproxy_category.sh5
-rwxr-xr-xsrc/conf_mode/service_webproxy.py209
-rwxr-xr-xsrc/legacy/vyatta-sg-blacklist.pl682
-rwxr-xr-xsrc/op_mode/webproxy_update_blacklist.py93
17 files changed, 2143 insertions, 4 deletions
diff --git a/data/templates/squid/sg_acl.conf.tmpl b/data/templates/squid/sg_acl.conf.tmpl
new file mode 100644
index 000000000..cb1c3ccb0
--- /dev/null
+++ b/data/templates/squid/sg_acl.conf.tmpl
@@ -0,0 +1,18 @@
+### generated by service_webproxy.py ###
+dbhome {{ squidguard_db_dir }}
+
+dest {{ category }}-{{ rule }} {
+{% if list_type == 'domains' %}
+ domainlist {{ category }}/domains
+{% elif list_type == 'urls' %}
+ urllist {{ category }}/urls
+{% elif list_type == 'expressions' %}
+ expressionlist {{ category }}/expressions
+{% endif %}
+}
+
+acl {
+ default {
+ pass all
+ }
+}
diff --git a/data/templates/squid/squid.conf.tmpl b/data/templates/squid/squid.conf.tmpl
new file mode 100644
index 000000000..8754e762d
--- /dev/null
+++ b/data/templates/squid/squid.conf.tmpl
@@ -0,0 +1,115 @@
+### generated by service_webproxy.py ###
+
+acl localhost src 127.0.0.1/32
+acl to_localhost dst 127.0.0.0/8
+acl net src all
+acl SSL_ports port 443
+acl Safe_ports port 80 # http
+acl Safe_ports port 21 # ftp
+acl Safe_ports port 443 # https
+acl Safe_ports port 873 # rsync
+acl Safe_ports port 70 # gopher
+acl Safe_ports port 210 # wais
+acl Safe_ports port 1025-65535 # unregistered ports
+acl Safe_ports port 280 # http-mgmt
+acl Safe_ports port 488 # gss-http
+acl Safe_ports port 591 # filemaker
+acl Safe_ports port 777 # multiling http
+acl CONNECT method CONNECT
+
+{% if authentication is defined and authentication is not none %}
+{% if authentication.children is defined and authentication.children is not none %}
+auth_param basic children {{ authentication.children }}
+{% endif %}
+{% if authentication.credentials_ttl is defined and authentication.credentials_ttl is not none %}
+auth_param basic credentialsttl {{ authentication.credentials_ttl }} minute
+{% endif %}
+{% if authentication.realm is defined and authentication.realm is not none %}
+auth_param basic realm "{{ authentication.realm }}"
+{% endif %}
+{# LDAP based Authentication #}
+{% if authentication.method is defined and authentication.method is not none %}
+{% if authentication.ldap is defined and authentication.ldap is not none and authentication.method == 'ldap' %}
+auth_param basic program /usr/lib/squid/basic_ldap_auth -v {{ authentication.ldap.version }} -b "{{ authentication.ldap.base_dn }}" {{ '-D "' + authentication.ldap.bind_dn + '"' if authentication.ldap.bind_dn is defined }} {{ '-w "' + authentication.ldap.password + '"' if authentication.ldap.password is defined }} {{ '-f "' + authentication.ldap.filter_expression + '"' if authentication.ldap.filter_expression is defined }} {{ '-u "' + authentication.ldap.username_attribute + '"' if authentication.ldap.username_attribute is defined }} -p {{ authentication.ldap.port }} {{ '-ZZ' if authentication.ldap.use_ssl is defined }} -R -h "{{ authentication.ldap.server }}"
+{% endif %}
+acl auth proxy_auth REQUIRED
+http_access allow auth
+{% endif %}
+{% endif %}
+
+http_access allow manager localhost
+http_access deny manager
+http_access deny !Safe_ports
+http_access deny CONNECT !SSL_ports
+http_access allow localhost
+http_access allow net
+http_access deny all
+
+{% if reply_block_mime is defined and reply_block_mime is not none %}
+{% for mime_type in reply_block_mime %}
+acl BLOCK_MIME rep_mime_type {{ mime_type }}
+{% endfor %}
+http_reply_access deny BLOCK_MIME
+{% endif %}
+
+{% if cache_size is defined and cache_size is not none %}
+{% if cache_size | int > 0 %}
+cache_dir ufs /var/spool/squid {{ cache_size }} 16 256
+{% else %}
+# disabling disk cache
+{% endif %}
+{% endif %}
+{% if mem_cache_size is defined and mem_cache_size is not none %}
+cache_mem {{ mem_cache_size }} MB
+{% endif %}
+{% if disable_access_log is defined %}
+access_log none
+{% else %}
+access_log /var/log/squid/access.log squid
+{% endif %}
+
+{# by default we'll disable the store log #}
+cache_store_log none
+
+{% if append_domain is defined and append_domain is not none %}
+append_domain {{ append_domain }}
+{% endif %}
+{% if maximum_object_size is defined and maximum_object_size is not none %}
+maximum_object_size {{ maximum_object_size }} KB
+{% endif %}
+{% if minimum_object_size is defined and minimum_object_size is not none %}
+minimum_object_size {{ minimum_object_size }} KB
+{% endif %}
+{% if reply_body_max_size is defined and reply_body_max_size is not none %}
+reply_body_max_size {{ reply_body_max_size }} KB
+{% endif %}
+{% if outgoing_address is defined and outgoing_address is not none %}
+tcp_outgoing_address {{ outgoing_address }}
+{% endif %}
+
+
+{% if listen_address is defined and listen_address is not none %}
+{% for address, config in listen_address.items() %}
+http_port {{ address }}:{{ config.port if config.port is defined else default_port }} {{ 'intercept' if config.disable_transparent is not defined }}
+{% endfor %}
+{% endif %}
+http_port 127.0.0.1:{{ default_port }}
+
+{# NOT insert the client address in X-Forwarded-For header #}
+forwarded_for off
+
+{# SquidGuard #}
+{% if url_filtering is defined and url_filtering.disable is not defined %}
+{% if url_filtering.squidguard is defined and url_filtering.squidguard is not none %}
+redirect_program /usr/bin/squidGuard -c {{ squidguard_conf }}
+redirect_children 8
+redirector_bypass on
+{% endif %}
+{% endif %}
+
+{% if cache_peer is defined and cache_peer is not none %}
+{% for peer, config in cache_peer.items() %}
+cache_peer {{ config.address }} {{ config.type }} {{ config.http_port }} {{ config.icp_port }} {{ config.options }}
+{% endfor %}
+never_direct allow all
+{% endif %}
diff --git a/data/templates/squid/squidGuard.conf.tmpl b/data/templates/squid/squidGuard.conf.tmpl
new file mode 100644
index 000000000..74de3a651
--- /dev/null
+++ b/data/templates/squid/squidGuard.conf.tmpl
@@ -0,0 +1,91 @@
+### generated by service_webproxy.py ###
+
+{% macro sg_rule(category, log, db_dir) %}
+{% set expressions = db_dir + '/' + category + '/expressions' %}
+dest {{ category }}-default {
+ domainlist {{ category }}/domains
+ urllist {{ category }}/urls
+{% if expressions | is_file %}
+ expressionlist {{ category }}/expressions
+{% endif %}
+{% if log is defined %}
+ log blacklist.log
+{% endif %}
+}
+{% endmacro %}
+
+{% if url_filtering is defined and url_filtering.disable is not defined %}
+{% if url_filtering.squidguard is defined and url_filtering.squidguard is not none %}
+{% set sg_config = url_filtering.squidguard %}
+{% set acl = namespace(value='local-ok-default') %}
+{% set acl.value = acl.value + ' !in-addr' if sg_config.allow_ipaddr_url is not defined else acl.value %}
+dbhome {{ squidguard_db_dir }}
+logdir /var/log/squid
+
+rewrite safesearch {
+ s@(.*\.google\..*/(custom|search|images|groups|news)?.*q=.*)@\1\&safe=active@i
+ s@(.*\..*/yandsearch?.*text=.*)@\1\&fyandex=1@i
+ s@(.*\.yahoo\..*/search.*p=.*)@\1\&vm=r@i
+ s@(.*\.live\..*/.*q=.*)@\1\&adlt=strict@i
+ s@(.*\.msn\..*/.*q=.*)@\1\&adlt=strict@i
+ s@(.*\.bing\..*/search.*q=.*)@\1\&adlt=strict@i
+ log rewrite.log
+}
+
+{% if sg_config.local_ok is defined and sg_config.local_ok is not none %}
+{% set acl.value = acl.value + ' local-ok-default' %}
+dest local-ok-default {
+ domainlist local-ok-default/domains
+}
+{% endif %}
+{% if sg_config.local_ok_url is defined and sg_config.local_ok_url is not none %}
+{% set acl.value = acl.value + ' local-ok-url-default' %}
+dest local-ok-url-default {
+ urllist local-ok-url-default/urls
+}
+{% endif %}
+{% if sg_config.local_block is defined and sg_config.local_block is not none %}
+{% set acl.value = acl.value + ' !local-block-default' %}
+dest local-block-default {
+ domainlist local-block-default/domains
+}
+{% endif %}
+{% if sg_config.local_block_url is defined and sg_config.local_block_url is not none %}
+{% set acl.value = acl.value + ' !local-block-url-default' %}
+dest local-block-url-default {
+ urllist local-block-url-default/urls
+}
+{% endif %}
+{% if sg_config.local_block_keyword is defined and sg_config.local_block_keyword is not none %}
+{% set acl.value = acl.value + ' !local-block-keyword-default' %}
+dest local-block-keyword-default {
+ expressionlist local-block-keyword-default/expressions
+}
+{% endif %}
+
+{% if sg_config.block_category is defined and sg_config.block_category is not none %}
+{% for category in sg_config.block_category %}
+{{ sg_rule(category, sg_config.log, squidguard_db_dir) }}
+{% set acl.value = acl.value + ' !' + category + '-default' %}
+{% endfor %}
+{% endif %}
+{% if sg_config.allow_category is defined and sg_config.allow_category is not none %}
+{% for category in sg_config.allow_category %}
+{{ sg_rule(category, False, squidguard_db_dir) }}
+{% set acl.value = acl.value + ' ' + category + '-default' %}
+{% endfor %}
+{% endif %}
+acl {
+ default {
+{% if sg_config.enable_safe_search is defined %}
+ rewrite safesearch
+{% endif %}
+ pass {{ acl.value }} {{ 'none' if sg_config.default_action is defined and sg_config.default_action == 'block' else 'allow' }}
+ redirect 302:http://{{ sg_config.redirect_url }}
+{% if sg_config.log is defined and sg_config.log is not none %}
+ log blacklist.log
+{% endif %}
+ }
+}
+{% endif %}
+{% endif %}
diff --git a/debian/control b/debian/control
index dc5094e8e..9750f08e2 100644
--- a/debian/control
+++ b/debian/control
@@ -99,6 +99,7 @@ Depends:
python3-pyudev,
python3-six,
python3-tabulate,
+ python3-tqdm,
python3-vici (>= 5.7.2),
python3-voluptuous,
python3-waitress,
@@ -108,6 +109,9 @@ Depends:
salt-minion,
snmp,
snmpd,
+ squid,
+ squidclient,
+ squidguard,
ssl-cert,
systemd,
tcpdump,
diff --git a/debian/rules b/debian/rules
index ab0df7201..5e275b8a3 100755
--- a/debian/rules
+++ b/debian/rules
@@ -51,6 +51,10 @@ override_dh_auto_install:
mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/completion
cp -r src/completion/* $(DIR)/$(VYOS_LIBEXEC_DIR)/completion
+ # Install legacy components
+ mkdir -p $(DIR)/$(VYOS_LIBEXEC_DIR)/legacy
+ cp -r src/legacy/* $(DIR)/$(VYOS_LIBEXEC_DIR)/legacy
+
# Install helper scripts
cp -r src/helpers/* $(DIR)/$(VYOS_LIBEXEC_DIR)/
diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install
index bfc30f7e6..691fa4731 100644
--- a/debian/vyos-1x.install
+++ b/debian/vyos-1x.install
@@ -21,5 +21,6 @@ usr/libexec/vyos/op_mode
usr/libexec/vyos/services
usr/libexec/vyos/system
usr/libexec/vyos/validators
+usr/libexec/vyos/legacy
usr/libexec/vyos/*.py
usr/share
diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst
index dc129cb54..92948de12 100644
--- a/debian/vyos-1x.postinst
+++ b/debian/vyos-1x.postinst
@@ -30,3 +30,6 @@ if ! grep -q '^dhcpd' /etc/passwd; then
adduser --quiet --system --disabled-login --no-create-home --home /run/dhcp-server dhcpd
adduser --quiet dhcpd hostsd
fi
+
+# ensure hte proxy user has a proper shell
+chsh -s /bin/sh proxy
diff --git a/interface-definitions/include/webproxy-url-filtering.xml.i b/interface-definitions/include/webproxy-url-filtering.xml.i
new file mode 100644
index 000000000..de6ebffde
--- /dev/null
+++ b/interface-definitions/include/webproxy-url-filtering.xml.i
@@ -0,0 +1,119 @@
+<!-- included start from webproxy-url-filtering.xml.i -->
+<leafNode name="allow-category">
+ <properties>
+ <help>Category to allow</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_webproxy_category.sh</script>
+ </completionHelp>
+ <multi/>
+ </properties>
+</leafNode>
+<leafNode name="allow-ipaddr-url">
+ <properties>
+ <help>Allow IP address URLs</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<leafNode name="block-category">
+ <properties>
+ <help>Category to block</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_webproxy_category.sh</script>
+ </completionHelp>
+ <multi/>
+ </properties>
+</leafNode>
+<leafNode name="default-action">
+ <properties>
+ <help>Default action (default: allow)</help>
+ <completionHelp>
+ <list>allow block</list>
+ </completionHelp>
+ <valueHelp>
+ <format>allow</format>
+ <description>Default filter action is allow)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>block</format>
+ <description>Default filter action is block</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(allow|block)$</regex>
+ </constraint>
+ </properties>
+</leafNode>
+<leafNode name="enable-safe-search">
+ <properties>
+ <help>Enable safe-mode search on popular search engines</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<leafNode name="local-block-keyword">
+ <properties>
+ <help>Local keyword to block</help>
+ <valueHelp>
+ <format>keyword</format>
+ <description>Keyword (or regex) to block</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+</leafNode>
+<leafNode name="local-block-url">
+ <properties>
+ <help>Local URL to block</help>
+ <valueHelp>
+ <format>url</format>
+ <description>Local URL to block (without "http://")</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+</leafNode>
+<leafNode name="local-block">
+ <properties>
+ <help>Local site to block</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IP address of site to block</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="fqdn"/>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<leafNode name="local-ok-url">
+ <properties>
+ <help>Local URL to allow</help>
+ <valueHelp>
+ <format>url</format>
+ <description>Local URL to allow (without "http://")</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+</leafNode>
+<leafNode name="local-ok">
+ <properties>
+ <help>Local site to allow</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IP address of site to allow</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="fqdn"/>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<leafNode name="log">
+ <properties>
+ <help>Log block category</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_webproxy_category.sh</script>
+ <list>all</list>
+ </completionHelp>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- included end -->
diff --git a/interface-definitions/service_webproxy.xml.in b/interface-definitions/service_webproxy.xml.in
new file mode 100644
index 000000000..4cd8138ec
--- /dev/null
+++ b/interface-definitions/service_webproxy.xml.in
@@ -0,0 +1,644 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="webproxy" owner="${vyos_conf_scripts_dir}/service_webproxy.py">
+ <properties>
+ <help>Webproxy service settings</help>
+ <priority>500</priority>
+ </properties>
+ <children>
+ <leafNode name="append-domain">
+ <properties>
+ <help>Default domain name</help>
+ <valueHelp>
+ <format>domain</format>
+ <description>Domain to use for urls that do not contain a '.'</description>
+ </valueHelp>
+ <constraint>
+ <regex>^[\.][a-z0-9-][$]?</regex>
+ </constraint>
+ <constraintErrorMessage>Must start append-domain with a '.'</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <node name="authentication">
+ <properties>
+ <help>Proxy Authentication Settings</help>
+ </properties>
+ <children>
+ <leafNode name="children">
+ <properties>
+ <help>Number of authentication helper processes (default: 5)</help>
+ <valueHelp>
+ <format>n</format>
+ <description>Number of authentication helper processes</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-500"/>
+ </constraint>
+ </properties>
+ <defaultValue>5</defaultValue>
+ </leafNode>
+ <leafNode name="credentials-ttl">
+ <properties>
+ <help>Authenticated session time to live in minutes (default: 60)</help>
+ <valueHelp>
+ <format>n</format>
+ <description>Authenticated session timeout</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-600"/>
+ </constraint>
+ </properties>
+ <defaultValue>60</defaultValue>
+ </leafNode>
+ <node name="ldap">
+ <properties>
+ <help>LDAP authentication settings</help>
+ </properties>
+ <children>
+ <leafNode name="base-dn">
+ <properties>
+ <help>LDAP Base DN to search</help>
+ </properties>
+ </leafNode>
+ <leafNode name="bind-dn">
+ <properties>
+ <help>LDAP DN used to bind to server</help>
+ </properties>
+ </leafNode>
+ <leafNode name="filter-expression">
+ <properties>
+ <help>Filter expression to perform LDAP search with</help>
+ </properties>
+ </leafNode>
+ <leafNode name="password">
+ <properties>
+ <help>LDAP password to bind with</help>
+ </properties>
+ </leafNode>
+ <leafNode name="persistent-connection">
+ <properties>
+ <help>Use persistent LDAP connection</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="port">
+ <properties>
+ <help>LDAP server port to use (default: 389)</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Port number to use</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>389</defaultValue>
+ </leafNode>
+ <leafNode name="server">
+ <properties>
+ <help>LDAP server to use</help>
+ </properties>
+ </leafNode>
+ <leafNode name="use-ssl">
+ <properties>
+ <help>Use SSL/TLS for LDAP connection</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="username-attribute">
+ <properties>
+ <help>LDAP username attribute</help>
+ </properties>
+ </leafNode>
+ <leafNode name="version">
+ <properties>
+ <help>LDAP protocol version (default: 3)</help>
+ <completionHelp>
+ <list>2 3</list>
+ </completionHelp>
+ <valueHelp>
+ <format>2</format>
+ <description>LDAP protocol version 2</description>
+ </valueHelp>
+ <valueHelp>
+ <format>3</format>
+ <description>LDAP protocol version 2</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 2-3"/>
+ </constraint>
+ </properties>
+ <defaultValue>3</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="method">
+ <properties>
+ <help>Authentication Method</help>
+ <completionHelp>
+ <list>ldap</list>
+ </completionHelp>
+ <valueHelp>
+ <format>ldap</format>
+ <description>Lightweight Directory Access Protocol</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(ldap)$</regex>
+ </constraint>
+ <constraintErrorMessage>The only supported method currently is LDAP</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="realm">
+ <properties>
+ <help>Name of authentication realm (e.g. "My Company proxy server")</help>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <tagNode name="cache-peer">
+ <properties>
+ <help>Specify other caches in a hierarchy</help>
+ <valueHelp>
+ <format>hostname</format>
+ <description>Cache peers FQDN</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <help>Hostname or IP address of peer</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>Squid cache-peer IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hostname</format>
+ <description>Squid cache-peer hostname</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-address"/>
+ <validator name="fqdn"/>
+ </constraint>
+ <constraintErrorMessage>Invalid FQDN or IP address</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="http-port">
+ <properties>
+ <help>Default Proxy Port (default: 3128)</help>
+ <valueHelp>
+ <format>u32:1025-65535</format>
+ <description>Default port number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1025-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>3128</defaultValue>
+ </leafNode>
+ <leafNode name="icp-port">
+ <properties>
+ <help>Cache peer ICP port (default: disabled)</help>
+ <valueHelp>
+ <format>u32:1-65535</format>
+ <description>Cache peer ICP port</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>0</defaultValue>
+ </leafNode>
+ <leafNode name="options">
+ <properties>
+ <help>Cache peer options (default: "no-query default")</help>
+ <valueHelp>
+ <format>text</format>
+ <description>Cache peer options</description>
+ </valueHelp>
+ </properties>
+ <defaultValue>no-query default</defaultValue>
+ </leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>Squid peer type (default parent)</help>
+ <completionHelp>
+ <list>parent sibling multicast</list>
+ </completionHelp>
+ <valueHelp>
+ <format>parent</format>
+ <description>Peer is a parent</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sibling</format>
+ <description>Peer is a sibling</description>
+ </valueHelp>
+ <valueHelp>
+ <format>multicast</format>
+ <description>Peer is a member of a multicast group</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(parent|sibling|multicast)$</regex>
+ </constraint>
+ </properties>
+ <defaultValue>parent</defaultValue>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="cache-size">
+ <properties>
+ <help>Disk cache size in MB (default: 100)</help>
+ <valueHelp>
+ <format>u32</format>
+ <description>Disk cache size in MB</description>
+ </valueHelp>
+ <valueHelp>
+ <format>0</format>
+ <description>Disable disk caching</description>
+ </valueHelp>
+ </properties>
+ <defaultValue>100</defaultValue>
+ </leafNode>
+ <leafNode name="default-port">
+ <properties>
+ <help>Default Proxy Port (default: 3128)</help>
+ <valueHelp>
+ <format>u32:1025-65535</format>
+ <description>Default port number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1025-65535"/>
+ </constraint>
+ </properties>
+ <defaultValue>3128</defaultValue>
+ </leafNode>
+ <leafNode name="disable-access-log">
+ <properties>
+ <help>Disable logging of HTTP accesses</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="domain-block">
+ <properties>
+ <help>Domain name to block</help>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="domain-noncache">
+ <properties>
+ <help>Domain name to access without caching</help>
+ <multi/>
+ </properties>
+ </leafNode>
+ <tagNode name="listen-address">
+ <properties>
+ <help>IPv4 listen-address for WebProxy [REQUIRED]</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_local_ips.sh --ipv4</script>
+ </completionHelp>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address listen on</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="port">
+ <properties>
+ <help>Default Proxy Port (default: 3128)</help>
+ <valueHelp>
+ <format>u32:1025-65535</format>
+ <description>Default port number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1025-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="disable-transparent">
+ <properties>
+ <help>Disable transparent mode</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="maximum-object-size">
+ <properties>
+ <help>Maximum size of object to be stored in cache in kilobytes</help>
+ <valueHelp>
+ <format>u32</format>
+ <description>Object size in KB</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-100000"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="mem-cache-size">
+ <properties>
+ <help>Memory cache size in MB</help>
+ <valueHelp>
+ <format>u32</format>
+ <description>Memory cache size in MB </description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-100000"/>
+ </constraint>
+ </properties>
+ <defaultValue>20</defaultValue>
+ </leafNode>
+ <leafNode name="minimum-object-size">
+ <properties>
+ <help>Maximum size of object to be stored in cache in kilobytes</help>
+ <valueHelp>
+ <format>u32</format>
+ <description>Object size in KB</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-100000"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="outgoing-address">
+ <properties>
+ <help>Outgoing IP address for webproxy</help>
+ </properties>
+ </leafNode>
+ <leafNode name="reply-block-mime">
+ <properties>
+ <help>MIME type to block</help>
+ <completionHelp>
+ <list>image/gif www/mime application/macbinary application/oda application/octet-stream application/pdf application/postscript application/postscript application/postscript text/rtf application/octet-stream application/octet-stream application/x-tar application/x-csh application/x-dvi application/x-hdf application/x-latex text/plain application/x-netcdf application/x-netcdf application/x-sh application/x-tcl application/x-tex application/x-texinfo application/x-texinfo application/x-troff application/x-troff application/x-troff application/x-troff-man application/x-troff-me application/x-troff-ms application/x-wais-source application/zip application/x-bcpio application/x-cpio application/x-gtar application/x-rpm application/x-shar application/x-sv4cpio application/x-sv4crc application/x-tar application/x-ustar audio/basic audio/basic audio/mpeg audio/mpeg audio/mpeg audio/x-aiff audio/x-aiff audio/x-aiff audio/x-wav image/bmp image/ief image/jpeg image/jpeg image/jpeg image/tiff image/tiff image/x-cmu-raster image/x-portable-anymap image/x-portable-bitmap image/x-portable-graymap image/x-portable-pixmap image/x-rgb image/x-xbitmap image/x-xpixmap image/x-xwindowdump text/html text/html text/css application/x-javascript text/plain text/plain text/plain text/plain text/plain text/plain text/plain text/plain text/plain text/richtext text/tab-separated-values text/x-setext video/mpeg video/mpeg video/mpeg video/quicktime video/quicktime video/x-msvideo video/x-sgi-movie application/mac-compactpro application/mac-binhex40 application/macwriteii application/msword application/msword application/vnd.ms-excel application/vnd.ms-powerpoint application/vnd.lotus-1-2-3 application/vnd.mif application/x-stuffit application/pict application/pict application/x-arj-compressed application/x-lha-compressed application/x-lha-compressed application/x-deflate text/plain application/octet-stream application/octet-stream image/png application/octet-stream application/x-xpinstall application/octet-stream text/plain application/x-director application/x-director application/x-director image/vnd.djvu image/vnd.djvu application/octet-stream application/octet-stream application/andrew-inset x-conference/x-cooltalk model/iges model/iges audio/midi audio/midi audio/midi model/mesh model/mesh video/vnd.mpegurl chemical/x-pdb application/x-chess-pgn audio/x-realaudio audio/x-pn-realaudio audio/x-pn-realaudio text/sgml text/sgml application/x-koan application/x-koan application/x-koan application/x-koan application/smil application/smil application/octet-stream application/x-futuresplash application/x-shockwave-flash application/x-cdlink model/vrml image/vnd.wap.wbmp application/vnd.wap.wbxml application/vnd.wap.wmlc application/vnd.wap.wmlscriptc application/vnd.wap.wmlscript application/xhtml application/xhtml text/xml text/xml chemical/x-xyz text/plain</list>
+ </completionHelp>
+ <constraint>
+ <regex>^(image/gif|www/mime|application/macbinary|application/oda|application/octet-stream|application/pdf|application/postscript|application/postscript|application/postscript|text/rtf|application/octet-stream|application/octet-stream|application/x-tar|application/x-csh|application/x-dvi|application/x-hdf|application/x-latex|text/plain|application/x-netcdf|application/x-netcdf|application/x-sh|application/x-tcl|application/x-tex|application/x-texinfo|application/x-texinfo|application/x-troff|application/x-troff|application/x-troff|application/x-troff-man|application/x-troff-me|application/x-troff-ms|application/x-wais-source|application/zip|application/x-bcpio|application/x-cpio|application/x-gtar|application/x-rpm|application/x-shar|application/x-sv4cpio|application/x-sv4crc|application/x-tar|application/x-ustar|audio/basic|audio/basic|audio/mpeg|audio/mpeg|audio/mpeg|audio/x-aiff|audio/x-aiff|audio/x-aiff|audio/x-wav|image/bmp|image/ief|image/jpeg|image/jpeg|image/jpeg|image/tiff|image/tiff|image/x-cmu-raster|image/x-portable-anymap|image/x-portable-bitmap|image/x-portable-graymap|image/x-portable-pixmap|image/x-rgb|image/x-xbitmap|image/x-xpixmap|image/x-xwindowdump|text/html|text/html|text/css|application/x-javascript|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/plain|text/richtext|text/tab-separated-values|text/x-setext|video/mpeg|video/mpeg|video/mpeg|video/quicktime|video/quicktime|video/x-msvideo|video/x-sgi-movie|application/mac-compactpro|application/mac-binhex40|application/macwriteii|application/msword|application/msword|application/vnd.ms-excel|application/vnd.ms-powerpoint|application/vnd.lotus-1-2-3|application/vnd.mif|application/x-stuffit|application/pict|application/pict|application/x-arj-compressed|application/x-lha-compressed|application/x-lha-compressed|application/x-deflate|text/plain|application/octet-stream|application/octet-stream|image/png|application/octet-stream|application/x-xpinstall|application/octet-stream|text/plain|application/x-director|application/x-director|application/x-director|image/vnd.djvu|image/vnd.djvu|application/octet-stream|application/octet-stream|application/andrew-inset|x-conference/x-cooltalk|model/iges|model/iges|audio/midi|audio/midi|audio/midi|model/mesh|model/mesh|video/vnd.mpegurl|chemical/x-pdb|application/x-chess-pgn|audio/x-realaudio|audio/x-pn-realaudio|audio/x-pn-realaudio|text/sgml|text/sgml|application/x-koan|application/x-koan|application/x-koan|application/x-koan|application/smil|application/smil|application/octet-stream|application/x-futuresplash|application/x-shockwave-flash|application/x-cdlink|model/vrml|image/vnd.wap.wbmp|application/vnd.wap.wbxml|application/vnd.wap.wmlc|application/vnd.wap.wmlscriptc|application/vnd.wap.wmlscript|application/xhtml|application/xhtml|text/xml|text/xml|chemical/x-xyz|text/plain)$</regex>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="reply-body-max-size">
+ <properties>
+ <help>Maximum reply body size in KB</help>
+ <valueHelp>
+ <format>u32</format>
+ <description>Reply size in KB</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-100000"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="url-filtering">
+ <properties>
+ <help>URL filtering settings</help>
+ </properties>
+ <children>
+ <leafNode name="disable">
+ <properties>
+ <help>Disable URL filtering</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <node name="squidguard">
+ <properties>
+ <help>URL filtering via squidGuard redirector</help>
+ </properties>
+ <children>
+ #include <include/webproxy-url-filtering.xml.i>
+ <node name="auto-update">
+ <properties>
+ <help>Auto update settings</help>
+ </properties>
+ <children>
+ <leafNode name="update-hour">
+ <properties>
+ <help>Hour of day for database update [REQUIRED]</help>
+ <valueHelp>
+ <format>u32:0-23</format>
+ <description>Hour for database update</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-23"/>
+ </constraint>
+ </properties>
+ <defaultValue>0</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="redirect-url">
+ <properties>
+ <help>Redirect URL for filtered websites (default: block.vyos.net)</help>
+ <valueHelp>
+ <format>url</format>
+ <description>URL for redirect</description>
+ </valueHelp>
+ </properties>
+ <defaultValue>block.vyos.net</defaultValue>
+ </leafNode>
+ <tagNode name="rule">
+ <properties>
+ <help>URL filter rule for a source-group</help>
+ <valueHelp>
+ <format>u32:1-1024</format>
+ <description>Rule Number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-1024"/>
+ </constraint>
+ <constraintErrorMessage>SquidGuard rule must between 1-1024</constraintErrorMessage>
+ </properties>
+ <children>
+ #include <include/webproxy-url-filtering.xml.i>
+ <leafNode name="redirect-url">
+ <properties>
+ <help>Redirect URL for filtered websites</help>
+ <valueHelp>
+ <format>url</format>
+ <description>URL for redirect</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="source-group">
+ <properties>
+ <help>Source-group for this rule [REQUIRED]</help>
+ <valueHelp>
+ <format>group</format>
+ <description>Source group identifier for this rule</description>
+ </valueHelp>
+ <completionHelp>
+ <path>service webproxy url-filtering squidguard source-group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="time-period">
+ <properties>
+ <help>Time-period for this rule</help>
+ <valueHelp>
+ <format>period</format>
+ <description>Time period for this rule</description>
+ </valueHelp>
+ <completionHelp>
+ <path>service webproxy url-filtering squidguard time-period</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <tagNode name="source-group">
+ <properties>
+ <help>Source group name</help>
+ <valueHelp>
+ <format>name</format>
+ <description>Name of source group</description>
+ </valueHelp>
+ <constraint>
+ <regex>^[^0-9]</regex>
+ </constraint>
+ <constraintErrorMessage>URL-filter source-group cannot start with a number!</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <help>Address for source-group</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 prefix to match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv4range</format>
+ <description>IPv4 address range to match</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv4-prefix"/>
+ <validator name="ipv4-range"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="description">
+ <properties>
+ <help>Description for source-group</help>
+ </properties>
+ </leafNode>
+ <leafNode name="domain">
+ <properties>
+ <help>Domain for source-group</help>
+ <valueHelp>
+ <format>domain</format>
+ <description>Domain name for the source-group</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="ldap-ip-search">
+ <properties>
+ <help>LDAP search expression for an IP address list</help>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="ldap-user-search">
+ <properties>
+ <help>LDAP search expression for a user group</help>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="user">
+ <properties>
+ <help>List of user names</help>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <tagNode name="time-period">
+ <properties>
+ <help>Time period name</help>
+ </properties>
+ <children>
+ <tagNode name="days">
+ <properties>
+ <help>Time-period days</help>
+ <completionHelp>
+ <list>Sun Mon Tue Wed Thu Fri Sat weekdays weekend all</list>
+ </completionHelp>
+ <valueHelp>
+ <format>Sun</format>
+ <description>Sunday</description>
+ </valueHelp>
+ <valueHelp>
+ <format>Mon</format>
+ <description>Monday</description>
+ </valueHelp>
+ <valueHelp>
+ <format>Tue</format>
+ <description>Tuesday</description>
+ </valueHelp>
+ <valueHelp>
+ <format>Wed</format>
+ <description>Wednesday</description>
+ </valueHelp>
+ <valueHelp>
+ <format>Thu</format>
+ <description>Thursday</description>
+ </valueHelp>
+ <valueHelp>
+ <format>Fri</format>
+ <description>Friday</description>
+ </valueHelp>
+ <valueHelp>
+ <format>Sat</format>
+ <description>Saturday</description>
+ </valueHelp>
+ <valueHelp>
+ <format>weekdays</format>
+ <description>Monday through Friday</description>
+ </valueHelp>
+ <valueHelp>
+ <format>weekend</format>
+ <description>Saturday and Sunday</description>
+ </valueHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All days of the week</description>
+ </valueHelp>
+ <constraint>
+ <regex>^(Sun|Mon|Tue|Wed|Thu|Fri|Sat|weekdays|weekend|all)$</regex>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="time">
+ <properties>
+ <help>Time for time-period</help>
+ <valueHelp>
+ <format>&lt;hh:mm - hh:mm&gt;</format>
+ <description>Time range in 24hr time</description>
+ </valueHelp>
+ <constraint>
+ <!-- time range example: 12:00-13:00 -->
+ <regex>^(\d\d:\d\d)-(\d\d:\d\d)$</regex>
+ </constraint>
+ <constraintErrorMessage>Expected time format hh:mm - hh:mm in 24hr time</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="description">
+ <properties>
+ <help>Time-period description</help>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/webproxy.xml b/op-mode-definitions/webproxy.xml
new file mode 100644
index 000000000..bccffd0b3
--- /dev/null
+++ b/op-mode-definitions/webproxy.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="monitor">
+ <children>
+ <node name="webproxy">
+ <properties>
+ <help>Monitor WebProxy service</help>
+ </properties>
+ <command>${vyatta_bindir}/vyatta-monitor Webproxy squid</command>
+ <children>
+ <node name="access-log">
+ <properties>
+ <help>Monitor the last lines of the squid access log</help>
+ </properties>
+ <command>if [ -f /var/log/squid3/access.log ]; then sudo tail --follow=name /var/log/squid3/access.log; else echo "WebProxy cache-log does not exist"; fi</command>
+ </node>
+ <node name="background">
+ <properties>
+ <help>Monitor Webproxy in the background</help>
+ </properties>
+ <children>
+ <node name="start">
+ <properties>
+ <help>Start background monitoring of Webproxy</help>
+ </properties>
+ <command>${vyatta_bindir}/vyatta-monitor-background Webproxy squid</command>
+ </node>
+ <node name="stop">
+ <properties>
+ <help>Stop background monitoring of Webproxy</help>
+ </properties>
+ <command>${vyatta_bindir}/vyatta-monitor-background-stop Webproxy </command>
+ </node>
+ </children>
+ </node>
+ <node name="cache-log">
+ <properties>
+ <help>Monitor the last lines of the squid cache log</help>
+ </properties>
+ <command>if [ -f /var/log/squid3/cache.log ]; then sudo tail --follow=name /var/log/squid3/cache.log; else echo "WebProxy cache-log does not exist"; fi</command>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="restart">
+ <children>
+ <node name="webproxy">
+ <properties>
+ <help>Restart WebProxy service</help>
+ </properties>
+ <command>if cli-shell-api existsActive service webproxy; then sudo systemctl restart squid.service; else echo "Service WebProxy not configured"; fi</command>
+ </node>
+ </children>
+ </node>
+ <node name="show">
+ <children>
+ <node name="webproxy">
+ <properties>
+ <help>Show WebProxy information</help>
+ </properties>
+ <children>
+ <!-- missing blacklist command -->
+ <node name="blacklist">
+ <properties>
+ <help>Show webproxy blacklist information</help>
+ </properties>
+ <children>
+ <node name="categories">
+ <properties>
+ <help>Show webproxy blacklist categories</help>
+ </properties>
+ <command>${vyos_completion_dir}/list_webproxy_category.sh</command>
+ </node>
+ </children>
+ </node>
+ <node name="log">
+ <properties>
+ <help>Show contents of WebProxy access log</help>
+ </properties>
+ <command>if [ -e /var/log/squid/access.log ]; then sudo less $_vyatta_less_options --prompt="file %i of %m, page %dt of %D" -- `printf "%s\n" /var/log/squid/access.log* | sort -nr`; else echo "No WebProxy log"; fi</command>
+ </node>
+ <node name="update-log">
+ <properties>
+ <help>Show update log for url-filter database</help>
+ </properties>
+ <command>if [ -e /opt/vyatta/etc/config/url-filtering/squidguard/updatestatus ]; then cat /opt/vyatta/etc/config/url-filtering/squidguard/updatestatus; else echo "Update log not found"; fi</command>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="update">
+ <children>
+ <node name="webproxy">
+ <properties>
+ <help>Update WebProxy</help>
+ </properties>
+ <children>
+ <node name="blacklists">
+ <properties>
+ <help>Update the webproxy blacklist database</help>
+ </properties>
+ <children>
+ <tagNode name="category">
+ <properties>
+ <help>Update a category of the webproxy blacklist database</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_webproxy_category.sh</script>
+ </completionHelp>
+ </properties>
+ <command>sudo /usr/libexec/vyos/legacy/vyatta-sg-blacklist.pl --update-blacklist-category="$5"</command>
+ </tagNode>
+ </children>
+ <command>sudo /usr/libexec/vyos/legacy/vyatta-sg-blacklist.pl --update-blacklist</command>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 63d400642..bf087c223 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -248,7 +248,6 @@ def dec_ip(address, decrement):
from ipaddress import ip_interface
return str(ip_interface(address).ip - int(decrement))
-
@register_filter('isc_static_route')
def isc_static_route(subnet, router):
# https://ercpe.de/blog/pushing-static-routes-with-isc-dhcp-server
@@ -270,3 +269,9 @@ def isc_static_route(subnet, router):
string += ','.join(router.split('.'))
return string
+
+@register_filter('is_file')
+def is_file(filename):
+ if os.path.exists(filename):
+ return os.path.isfile(filename)
+ return False
diff --git a/python/vyos/util.py b/python/vyos/util.py
index fc6915687..494c8155e 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -215,6 +215,30 @@ def read_file(fname, defaultonfailure=None):
return defaultonfailure
raise e
+def write_file(fname, data, defaultonfailure=None, user=None, group=None):
+ """
+ Write content of data to given fname, should defaultonfailure be not None,
+ it is returned on failure to read.
+
+ If directory of file is not present, it is auto-created.
+ """
+ dirname = os.path.dirname(fname)
+ if not os.path.isdir(dirname):
+ os.makedirs(dirname, mode=0o755, exist_ok=False)
+ chown(dirname, user, group)
+
+ try:
+ """ Write a file to string """
+ bytes = 0
+ with open(fname, 'w') as f:
+ bytes = f.write(data)
+ chown(fname, user, group)
+ return bytes
+ except Exception as e:
+ if defaultonfailure is not None:
+ return defaultonfailure
+ raise e
+
def read_json(fname, defaultonfailure=None):
"""
diff --git a/smoketest/scripts/cli/test_service_webproxy.py b/smoketest/scripts/cli/test_service_webproxy.py
index dac5aa0dd..6f88a351d 100755
--- a/smoketest/scripts/cli/test_service_webproxy.py
+++ b/smoketest/scripts/cli/test_service_webproxy.py
@@ -127,7 +127,7 @@ class TestServiceWebProxy(unittest.TestCase):
realm = 'VyOS Webproxy'
ldap_base_dn = 'DC=vyos,DC=net'
ldap_server = 'ldap.vyos.net'
- ldap_bind_dn = 'CN=proxyuser,CN=Users,DC=example,DC=local'
+ ldap_bind_dn = f'CN=proxyuser,CN=Users,{ldap_base_dn}'
ldap_password = 'VyOS12345'
ldap_attr = 'cn'
ldap_filter = '(cn=%s)'
@@ -156,6 +156,7 @@ class TestServiceWebProxy(unittest.TestCase):
self.session.set(base_path + ['authentication', 'ldap', 'username-attribute', ldap_attr])
self.session.set(base_path + ['authentication', 'ldap', 'filter-expression', ldap_filter])
+ self.session.set(base_path + ['authentication', 'ldap', 'use-ssl'])
# commit changes
self.session.commit()
@@ -166,8 +167,8 @@ class TestServiceWebProxy(unittest.TestCase):
# Now verify LDAP settings
self.assertIn(f'auth_param basic children {auth_children}', config)
self.assertIn(f'auth_param basic credentialsttl {cred_ttl} minute', config)
- self.assertIn(f'auth_param basic realm {realm}', config)
- self.assertIn(f'auth_param basic program /usr/lib/squid/basic_ldap_auth -v 3 -b "{ldap_base_dn}" -D "{ldap_bind_dn}" -w {ldap_password} -f {ldap_filter} -u {ldap_attr} -p 389 -R -h {ldap_server}', config)
+ self.assertIn(f'auth_param basic realm "{realm}"', config)
+ self.assertIn(f'auth_param basic program /usr/lib/squid/basic_ldap_auth -v 3 -b "{ldap_base_dn}" -D "{ldap_bind_dn}" -w "{ldap_password}" -f "{ldap_filter}" -u "{ldap_attr}" -p 389 -ZZ -R -h "{ldap_server}"', config)
self.assertIn(f'acl auth proxy_auth REQUIRED', config)
# Check for running process
diff --git a/src/completion/list_webproxy_category.sh b/src/completion/list_webproxy_category.sh
new file mode 100755
index 000000000..a5ad2398a
--- /dev/null
+++ b/src/completion/list_webproxy_category.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+DB_DIR="/opt/vyatta/etc/config/url-filtering/squidguard/db/"
+if [ -d ${DB_DIR} ]; then
+ ls -ald ${DB_DIR}/* | grep -E '^(d|l)' | awk '{print $9}' | sed s#${DB_DIR}/##
+fi
diff --git a/src/conf_mode/service_webproxy.py b/src/conf_mode/service_webproxy.py
new file mode 100755
index 000000000..8dfae348a
--- /dev/null
+++ b/src/conf_mode/service_webproxy.py
@@ -0,0 +1,209 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# 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/>.
+
+import os
+
+from shutil import rmtree
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.template import render
+from vyos.util import call
+from vyos.util import dict_search
+from vyos.util import write_file
+from vyos.validate import is_addr_assigned
+from vyos.xml import defaults
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+squid_config_file = '/etc/squid/squid.conf'
+squidguard_config_file = '/etc/squidguard/squidGuard.conf'
+squidguard_db_dir = '/opt/vyatta/etc/config/url-filtering/squidguard/db'
+user_group = 'proxy'
+
+def generate_sg_localdb(category, list_type, role, proxy):
+ cat_ = category.replace('-', '_')
+ if isinstance(dict_search(f'url_filtering.squidguard.{cat_}', proxy),
+ list):
+
+ # local block databases must be generated "on-the-fly"
+ tmp = {
+ 'squidguard_db_dir' : squidguard_db_dir,
+ 'category' : f'{category}-default',
+ 'list_type' : list_type,
+ 'rule' : role
+ }
+ sg_tmp_file = '/tmp/sg.conf'
+ db_file = f'{category}-default/{list_type}'
+ domains = '\n'.join(dict_search(f'url_filtering.squidguard.{cat_}', proxy))
+
+ # local file
+ write_file(f'{squidguard_db_dir}/{category}-default/local', '',
+ user=user_group, group=user_group)
+ # database input file
+ write_file(f'{squidguard_db_dir}/{db_file}', domains,
+ user=user_group, group=user_group)
+
+ # temporary config file, deleted after generation
+ render(sg_tmp_file, 'squid/sg_acl.conf.tmpl', tmp,
+ user=user_group, group=user_group)
+
+ call(f'su - {user_group} -c "squidGuard -d -c {sg_tmp_file} -C {db_file}"')
+
+ if os.path.exists(sg_tmp_file):
+ os.unlink(sg_tmp_file)
+
+ else:
+ # if category is not part of our configuration, clean out the
+ # squidguard lists
+ tmp = f'{squidguard_db_dir}/{category}-default'
+ if os.path.exists(tmp):
+ rmtree(f'{squidguard_db_dir}/{category}-default')
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['service', 'webproxy']
+ if not conf.exists(base):
+ return None
+
+ proxy = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(base)
+
+ # if no authentication method is supplied, no need to add defaults
+ if not dict_search('authentication.method', proxy):
+ default_values.pop('authentication')
+ # if no url_filteringurl-filtering method is supplied, no need to add defaults
+ if 'url_filtering' not in proxy:
+ default_values.pop('url_filtering')
+ else:
+ # store path to squidGuard config, used when generating Squid config
+ proxy['squidguard_conf'] = squidguard_config_file
+ proxy['squidguard_db_dir'] = squidguard_db_dir
+
+ # XXX: T2665: blend in proper cache-peer default values later
+ default_values.pop('cache_peer')
+ proxy = dict_merge(default_values, proxy)
+
+ # XXX: T2665: blend in proper cache-peer default values
+ if 'cache_peer' in proxy:
+ default_values = defaults(base + ['cache-peer'])
+ for peer in proxy['cache_peer']:
+ proxy['cache_peer'][peer] = dict_merge(default_values,
+ proxy['cache_peer'][peer])
+
+ return proxy
+
+def verify(proxy):
+ if not proxy:
+ return None
+
+ if 'listen_address' not in proxy:
+ raise ConfigError('listen-address needs to be configured!')
+
+ ldap_auth = dict_search('authentication.method', proxy) == 'ldap'
+
+ for address, config in proxy['listen_address'].items():
+ if not is_addr_assigned(address):
+ raise ConfigError(
+ f'listen-address "{address}" not assigned on any interface!')
+ if ldap_auth and 'disable_transparent' not in config:
+ raise ConfigError('Authentication can not be configured when ' \
+ 'proxy is in transparent mode')
+
+ if 'outgoing_address' in proxy:
+ address = proxy['outgoing_address']
+ if not is_addr_assigned(address):
+ raise ConfigError(
+ f'outgoing-address "{address}" not assigned on any interface!')
+
+ if 'authentication' in proxy:
+ if 'method' not in proxy['authentication']:
+ raise ConfigError('proxy authentication method required!')
+
+ if ldap_auth:
+ ldap_config = proxy['authentication']['ldap']
+
+ if 'server' not in ldap_config:
+ raise ConfigError(
+ 'LDAP authentication enabled, but no server set')
+
+ if 'password' in ldap_config and 'bind_dn' not in ldap_config:
+ raise ConfigError(
+ 'LDAP password can not be set when base-dn is undefined!')
+
+ if 'bind_dn' in ldap_config and 'password' not in ldap_config:
+ raise ConfigError(
+ 'LDAP bind DN can not be set without password!')
+
+ if 'base_dn' not in ldap_config:
+ raise ConfigError('LDAP base-dn must be set!')
+
+ if 'cache_peer' in proxy:
+ for peer, config in proxy['cache_peer'].items():
+ if 'address' not in config:
+ raise ConfigError(f'Cache-peer "{peer}" address must be set!')
+
+
+def generate(proxy):
+ if not proxy:
+ return None
+
+ render(squid_config_file, 'squid/squid.conf.tmpl', proxy)
+ render(squidguard_config_file, 'squid/squidGuard.conf.tmpl', proxy)
+
+ cat_dict = {
+ 'local-block' : 'domains',
+ 'local-block-keyword' : 'expressions',
+ 'local-block-url' : 'urls',
+ 'local-ok' : 'domains',
+ 'local-ok-url' : 'urls'
+ }
+ for category, list_type in cat_dict.items():
+ generate_sg_localdb(category, list_type, 'default', proxy)
+
+ return None
+
+def apply(proxy):
+ if not proxy:
+ # proxy is removed in the commit
+ call('systemctl stop squid.service')
+
+ if os.path.exists(squid_config_file):
+ os.unlink(squid_config_file)
+ if os.path.exists(squidguard_config_file):
+ os.unlink(squidguard_config_file)
+
+ return None
+
+ call('systemctl restart squid.service')
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/legacy/vyatta-sg-blacklist.pl b/src/legacy/vyatta-sg-blacklist.pl
new file mode 100755
index 000000000..4104ac266
--- /dev/null
+++ b/src/legacy/vyatta-sg-blacklist.pl
@@ -0,0 +1,682 @@
+#!/usr/bin/perl
+#
+# Module: vyatta-sg-blacklist.pl
+#
+# **** License ****
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# 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.
+#
+# This code was originally developed by Vyatta, Inc.
+# Portions created by Vyatta are Copyright (C) 2008-2009 Vyatta, Inc.
+# All Rights Reserved.
+#
+# Author: Stig Thormodsrud
+# Date: October 2008
+# Description: script to download/update free url blacklist.
+#
+# **** End License ****
+#
+
+use Getopt::Long;
+use POSIX;
+use IO::Prompt;
+use Sys::Syslog qw(:standard :macros);
+use File::Copy;
+use Fcntl qw(:flock);
+use base qw(Exporter);
+use File::Basename;
+use File::Compare;
+
+use lib "/opt/vyatta/share/perl5";
+use Vyatta::Config;
+use Vyatta::File;
+
+use warnings;
+use strict;
+
+#
+# Default blacklist
+#
+# Below are some free blacklists we've tried:
+#
+# http://squidguard.mesd.k12.or.us/blacklists.tgz
+# http://ftp.teledanmark.no/pub/www/proxy/squidguard/contrib/blacklists.tar.gz
+# ftp://ftp.univ-tlse1.fr/pub/reseau/cache/squidguard_contrib/blacklists.tar.gz
+#
+# Note: the auto install/update assumes that the blacklist url is a tar gz
+# file with the blacklist categorys in a "blacklist" directory. Some
+# of the commercially available blacklists are a cgi script instead, so
+# those blacklists will need a different install/update script. Of
+# course they can be manually installed/updated.
+#
+my $blacklist_url = 'ftp://ftp.univ-tlse1.fr/pub/reseau/cache/squidguard_contrib/blacklists.tar.gz';
+
+#squid globals
+my $squid_init = '/etc/init.d/squid';
+my $squid_mime_type = '/usr/share/squid/mime.conf';
+
+#squidGuard globals
+my $urlfilter_data_dir = '/opt/vyatta/etc/config/url-filtering';
+my $squidguard_blacklist_db = "$urlfilter_data_dir/squidguard/db";
+my $squidguard_log_dir = '/var/log/squid';
+my $squidguard_blacklist_log = "$squidguard_log_dir/blacklist.log";
+my $squidguard_safesearch = "/opt/vyatta/etc/safesearch_rewrites";
+
+#vyattaguard globals
+my $vyattaguard = '/opt/vyatta/sbin/vg';
+
+sub webproxy_get_global_data_dir {
+ return $urlfilter_data_dir;
+}
+
+my $global_data_dir = webproxy_get_global_data_dir();
+
+
+sub squid_get_mime {
+ my @mime_types = ();
+ open(my $FILE, "<", $squid_mime_type) or die "Error: read $!";
+ my @lines = <$FILE>;
+ close($FILE);
+ foreach my $line (@lines) {
+ next if $line =~ /^#/; # skip comments
+ if ($line =~ /^([\S]+)[\s]+([\S]+)[\s]+([\S]+)[\s]+([\S]+).*$/) {
+ my $type = $2;
+ push @mime_types, $type if $type =~ /\//;
+ }
+ }
+ return @mime_types;
+}
+
+sub squidguard_is_configured {
+ my $config = new Vyatta::Config;
+ $config->setLevel('service webproxy url-filtering');
+ # This checks the running config, so it is assumed
+ # to be called from op mode.
+ return 1 if $config->existsOrig('squidguard');
+ return 0;
+}
+
+sub squidguard_get_blacklist_dir {
+ return $squidguard_blacklist_db;
+}
+
+sub squidguard_get_blacklist_log {
+ return $squidguard_blacklist_log;
+}
+
+sub squidguard_get_safesearch_rewrites {
+ my @rewrites = ();
+ open(my $FILE, "<", $squidguard_safesearch) or die "Error: read $!";
+ my @lines = <$FILE>;
+ close($FILE);
+ chomp @lines;
+ foreach my $line (@lines) {
+ next if $line =~ /^#/; # skip comments
+ if ($line =~ /^s\@/) {
+ push @rewrites, $line;
+ }
+ }
+ return @rewrites;
+}
+
+sub squidguard_ec_get_categorys {
+ my %cat_hash;
+
+ die "Must enable vyattaguard" if ! squidguard_use_ec();
+ die "Missing vyattaguard package\n" if ! -e $vyattaguard;
+ exit 1 if ! -e "$urlfilter_data_dir/sitefilter/categories.txt";
+
+ my @lines = `$vyattaguard list`;
+ foreach my $line (@lines) {
+ my ($id, $category) = split ':', $line;
+ next if ! defined $category;
+ chomp $category;
+ $category =~ s/\s/\_/g;
+ $category =~ s/\&/\_and\_/g;
+ $cat_hash{$id} = $category;
+ }
+ return %cat_hash;
+}
+
+sub squidguard_ec_cat2name {
+ my ($cat) = @_;
+
+ my %cat_hash = squidguard_ec_get_categorys();
+ return $cat_hash{$cat} if defined $cat_hash{$cat};
+ return;
+}
+
+sub squidguard_ec_name2cat {
+ my ($name) = @_;
+
+ my %cat_hash = squidguard_ec_get_categorys();
+ foreach my $key (keys (%cat_hash)) {
+ if ($cat_hash{$key} eq $name) {
+ return $key;
+ }
+ }
+ return;
+}
+
+sub squidguard_use_ec {
+ my $rc = system("cli-shell-api inSession");
+ my ($exist_func, $value_func);
+ if ($rc == 0) {
+ $exist_func = 'exists';
+ $value_func = 'returnValue';
+ } else {
+ $exist_func = 'existsOrig';
+ $value_func = 'returnOrigValue';
+ }
+ my $config = new Vyatta::Config;
+ $config->setLevel('service webproxy url-filtering squidguard');
+ if ($config->$exist_func('vyattaguard')) {
+ return if ! -e $vyattaguard;
+ my $mode = $config->$value_func('vyattaguard mode');
+ return $mode;
+ }
+ return;
+}
+
+sub squidguard_get_blacklists {
+
+ my @blacklists = ();
+ if (squidguard_use_ec()) {
+ die "Missing vyattaguard package\n" if ! -e $vyattaguard;
+ my %cat_hash = squidguard_ec_get_categorys();
+ foreach my $key (keys (%cat_hash)) {
+ next if ! defined $cat_hash{$key};
+ push @blacklists, $cat_hash{$key};
+ }
+ } else {
+ my $dir = $squidguard_blacklist_db;
+ opendir(DIR, $dir) || die "can't opendir $dir: $!";
+ my @dirs = readdir(DIR);
+ closedir DIR;
+
+ foreach my $file (@dirs) {
+ next if $file eq '.';
+ next if $file eq '..';
+ if (-d "$dir/$file") {
+ push @blacklists, $file;
+ }
+ }
+ }
+ @blacklists = sort(@blacklists);
+ return @blacklists;
+}
+
+sub squidguard_generate_db {
+ my ($interactive, $category, $group) = @_;
+
+ my $db_dir = squidguard_get_blacklist_dir();
+ my $tmp_conf = "/tmp/sg.conf.$$";
+ my $output = "dbhome $db_dir\n";
+ $output .= squidguard_build_dest($category, 0, $group);
+ $output .= "\nacl {\n";
+ $output .= "\tdefault {\n";
+ $output .= "\t\tpass all\n";
+ $output .= "\t}\n}\n\n";
+ webproxy_write_file($tmp_conf, $output);
+
+ my $dir = "$db_dir/$category";
+ if ( -l $dir) {
+ print "Skip link for [$category] -> [", readlink($dir), "]\n"
+ if $interactive;
+ return;
+ }
+ foreach my $type ('domains', 'urls', 'expressions') {
+ my $path = "$category/$type";
+ my $file = "$db_dir/$path";
+ if (-e $file and -s _) { # check exists and non-zero
+ my $file_db = "$file.db";
+ if (! -e $file_db) {
+ #
+ # it appears that there is a bug in squidGuard that if
+ # the db file doesn't exist then running with -C leaves
+ # huge tmp files in /var/tmp.
+ #
+ system("touch $file.db");
+ system("chown -R proxy.proxy $file.db > /dev/null 2>&1");
+ }
+ my $wc = `cat $file| wc -l`; chomp $wc;
+ print "Building DB for [$path] - $wc entries\n" if $interactive;
+ my $cmd = "\"squidGuard -d -c $tmp_conf -C $path\"";
+ system("su - proxy -c $cmd > /dev/null 2>&1");
+ }
+ }
+ system("rm $tmp_conf");
+}
+
+sub squidguard_is_category_local {
+ my ($category) = @_;
+
+ my $db_dir = squidguard_get_blacklist_dir();
+ my $local_file = "$db_dir/$category/local";
+ return 1 if -e $local_file;
+ return 0;
+}
+
+sub squidguard_is_blacklist_installed {
+ if (squidguard_use_ec()) {
+ if (-e "$urlfilter_data_dir/sitefilter/urldb") {
+ return 1;
+ }
+ } else {
+ my @blacklists = squidguard_get_blacklists();
+ foreach my $category (@blacklists) {
+ next if squidguard_is_category_local($category);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+sub squidguard_get_blacklist_domains_urls_exps {
+ my ($list) = shift;
+
+ my $dir = $squidguard_blacklist_db;
+ my ($domains, $urls, $exps) = undef;
+ $domains = "$list/domains" if -f "$dir/$list/domains" && -s _;
+ $urls = "$list/urls" if -f "$dir/$list/urls" && -s _;
+ $exps = "$list/expressions" if -f "$dir/$list/expressions" && -s _;
+ return ($domains, $urls, $exps);
+}
+
+sub squidguard_get_blacklist_files {
+ my ($type, $category) = @_;
+
+ my @lists = squidguard_get_blacklists();
+ my @files = ();
+ foreach my $list (@lists) {
+ my ($domain, $url, $exp) = squidguard_get_blacklist_domains_urls_exps(
+ $list);
+ if ($type eq 'domains') {
+ next if !defined $domain;
+ if (defined $category) {
+ next if $domain ne "$category/domains";
+ }
+ $domain = "$squidguard_blacklist_db/$domain";
+ push @files, $domain;
+ }
+ if ($type eq 'urls') {
+ next if !defined $url;
+ if (defined $category) {
+ next if $url ne "$category/urls";
+ }
+ $url = "$squidguard_blacklist_db/$url";
+ push @files, $url;
+ }
+ if ($type eq 'expressions') {
+ next if !defined $exp;
+ if (defined $category) {
+ next if $url ne "$category/expressions";
+ }
+ $exp = "$squidguard_blacklist_db/$exp";
+ push @files, $exp;
+ }
+
+ }
+ @files = sort(@files);
+ return @files;
+}
+
+sub squidguard_get_log_files {
+ open(my $LS, "-|", "ls $squidguard_log_dir/bl*.log* 2> /dev/null | sort -nr ");
+ my @log_files = <$LS>;
+ close $LS;
+ chomp @log_files;
+ return @log_files;
+}
+
+sub squidguard_build_dest {
+ my ($category, $logging, $group, $ec) = @_;
+
+ my $output = '';
+ my ($domains, $urls, $exps);
+ if (squidguard_is_category_local("$category-$group")) {
+ ($domains, $urls, $exps) = squidguard_get_blacklist_domains_urls_exps(
+ "$category-$group");
+ } else {
+ ($domains, $urls, $exps) = squidguard_get_blacklist_domains_urls_exps(
+ $category);
+ }
+
+ my $ec_cat = undef;
+ if (defined $ec) {
+ $ec_cat = squidguard_ec_name2cat($category);
+ }
+
+ $output = "dest $category-$group {\n";
+ $output .= "\tdomainlist $domains\n" if defined $domains;
+ $output .= "\turllist $urls\n" if defined $urls;
+ $output .= "\texpressionlist $exps\n" if defined $exps;
+ $output .= "\teccategory $ec_cat\n" if defined $ec_cat;
+ if ($logging) {
+ my $log = basename($squidguard_blacklist_log);
+ $output .= "\tlog $log\n";
+ }
+ $output .= "}\n\n";
+ return $output;
+}
+
+sub webproxy_read_file {
+ my ($file) = @_;
+ my @lines;
+ if ( -e $file) {
+ open(my $FILE, '<', $file) or die "Error: read $!";
+ @lines = <$FILE>;
+ close($FILE);
+ chomp @lines;
+ }
+ return @lines;
+}
+
+sub is_same_as_file {
+ my ($file, $value) = @_;
+
+ return if ! -e $file;
+
+ my $mem_file = '';
+ open my $MF, '+<', \$mem_file or die "couldn't open memfile $!\n";
+ print $MF $value;
+ seek($MF, 0, 0);
+
+ my $rc = compare($file, $MF);
+ return 1 if $rc == 0;
+ return;
+}
+
+sub webproxy_write_file {
+ my ($file, $config) = @_;
+
+ # Avoid unnecessary writes. At boot the file will be the
+ # regenerated with the same content.
+ return if is_same_as_file($file, $config);
+
+ open(my $fh, '>', $file) || die "Couldn't open $file - $!";
+ print $fh $config;
+ close $fh;
+ return 1;
+}
+
+sub webproxy_append_file {
+ my ($dst, $src) = @_;
+
+ open(my $ih, '<', $src) || die "Couldn't open $src - $!";
+ open(my $oh, '>>', $dst) || die "Couldn't open $dst - $!";
+ for (<$ih>) {
+ print $oh $_;
+ }
+ close($oh);
+ close($ih);
+ return 1;
+}
+
+sub webproxy_delete_local_entry {
+ my ($file, $value) = @_;
+
+ my $db_dir = squidguard_get_blacklist_dir();
+ $file = "$db_dir/$file";
+ my @lines = webproxy_read_file($file);
+ my $config = '';
+ foreach my $line (@lines) {
+ $config .= "$line\n" if $line ne $value;
+ }
+ if ($config eq '') {
+ unlink($file);
+ } else {
+ webproxy_write_file($file, $config);
+ }
+ return;
+}
+
+sub webproxy_delete_all_local {
+ my $db_dir = squidguard_get_blacklist_dir();
+ my @categorys = squidguard_get_blacklists();
+ foreach my $category (@categorys) {
+ if (squidguard_is_category_local($category)) {
+ system("rm -rf $db_dir/$category");
+ }
+ }
+ return;
+}
+
+sub print_err {
+ my ($interactive, $msg) = @_;
+ if ($interactive) {
+ print "$msg\n";
+ } else {
+ syslog(LOG_ERR, $msg);
+ }
+}
+
+sub squidguard_count_blacklist_entries {
+ my $db_dir = squidguard_get_blacklist_dir();
+
+ my $total = 0;
+ my @categories = squidguard_get_blacklists();
+ foreach my $category (@categories) {
+ foreach my $type ('domains', 'urls') {
+ my $path = "$category/$type";
+ my $file = "$db_dir/$path";
+ if (-e $file) {
+ my $wc = `cat $file| wc -l`; chomp $wc;
+ $total += $wc;
+ }
+ }
+ }
+ return $total;
+}
+
+sub squidguard_clean_tmpfiles {
+ #
+ # workaround for squidguard
+ # bug http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=494281
+ #
+ my @tmpfiles = </var/tmp/*>;
+ foreach my $file (@tmpfiles) {
+ my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime,
+ $mtime, $ctime, $blksize, $blocks) = stat($file);
+ my $name = (getpwuid($uid))[0] if $uid;
+ unlink($file) if $name and $name eq 'proxy';
+ }
+}
+
+sub squidguard_auto_update {
+ my ($interactive, $file) = @_;
+
+ my $rc;
+ my $db_dir = squidguard_get_blacklist_dir();
+ my $tmp_blacklists = '/tmp/blacklists.gz';
+
+ if (!squidguard_is_blacklist_installed()) {
+ my ($disk_free, $disk_required);
+ $disk_required = (30 * 1024 * 1024); # 30MB
+ $disk_free = `df $db_dir | grep -v Filesystem | awk '{ print \$4 }'`;
+ chomp($disk_free);
+ $disk_free *= 1024;
+ if ($disk_free < $disk_required) {
+ die "Error: not enough disk space $disk_required\/$disk_free";
+ }
+ }
+
+ if (defined $file) {
+ # use existing file
+ $rc = copy($file, $tmp_blacklists);
+ if (!$rc) {
+ print_err($interactive, "Unable to copy [$file] $!");
+ return 1;
+ }
+ } else {
+ # get from net
+ my $opt = '';
+ $opt = "-q" if ! $interactive;
+ $rc = system("wget -O $tmp_blacklists $opt $blacklist_url");
+ if ($rc) {
+ print_err($interactive, "Unable to download [$blacklist_url] $!");
+ return 1;
+ }
+ }
+
+ print "Uncompressing blacklist...\n" if $interactive;
+ $rc = system("tar --directory /tmp -zxvf $tmp_blacklists > /dev/null");
+ if ($rc) {
+ print_err($interactive, "Unable to uncompress [$blacklist_url] $!");
+ return 1;
+ }
+ my $b4_entries = squidguard_count_blacklist_entries();
+ my $archive = "$global_data_dir/squidguard/archive";
+ mkdir_p($archive) if ! -d $archive;
+ system("rm -rf $archive/*");
+ system("mv $db_dir/* $archive 2> /dev/null");
+ $rc = system("mv /tmp/blacklists/* $db_dir");
+ if ($rc) {
+ print_err($interactive, "Unable to install [$blacklist_url] $!");
+ return 1;
+ }
+ system("mv $archive/local-* $db_dir 2> /dev/null");
+ rm_rf($tmp_blacklists);
+ rm_rf("/tmp/blacklists");
+
+ my $after_entries = squidguard_count_blacklist_entries();
+ my $mode = "auto-update";
+ $mode = "manual" if $interactive;
+ syslog(LOG_WARNING,
+ "blacklist entries updated($mode) ($b4_entries/$after_entries)");
+ return 0;
+}
+
+sub squidguard_install_blacklist_def {
+ squidguard_auto_update(1, undef);
+}
+
+sub squidguard_update_blacklist {
+ my ($interactive, $update_category) = @_;
+
+ my @blacklists = squidguard_get_blacklists();
+ print "Checking permissions...\n" if $interactive;
+ my $db_dir = squidguard_get_blacklist_dir();
+ system("chown -R proxy.proxy $db_dir > /dev/null 2>&1");
+ chmod(2770, $db_dir);
+
+ #
+ # generate temporary config for each category & generate DB
+ #
+ foreach my $category (@blacklists) {
+ next if defined $update_category and $update_category ne $category;
+ squidguard_generate_db($interactive, $category, 'default');
+ }
+}
+
+
+#
+# main
+#
+my ($update_bl, $update_bl_cat, $update_bl_file, $auto_update_bl);
+
+GetOptions("update-blacklist!" => \$update_bl,
+ "update-blacklist-category=s" => \$update_bl_cat,
+ "update-blacklist-file=s" => \$update_bl_file,
+ "auto-update-blacklist!" => \$auto_update_bl,
+);
+
+my $sg_updatestatus_file = "$global_data_dir/squidguard/updatestatus";
+if (! -e "$global_data_dir/squidguard") {
+ system("mkdir -p $global_data_dir/squidguard/db");
+ my ($login, $pass, $uid, $gid) = getpwnam('proxy')
+ or die "proxy not in passwd file";
+ chown $uid, $gid, "$global_data_dir/squidguard/db";
+}
+touch($sg_updatestatus_file);
+system("echo update failed at `date` > $sg_updatestatus_file");
+system("sudo rm -f /var/lib/sitefilter/updatestatus");
+
+my $lock_file = '/tmp/vyatta_bl_lock';
+open(my $lck, ">", $lock_file) || die "Lock failed\n";
+flock($lck, LOCK_EX);
+
+if (defined $update_bl_cat) {
+ squidguard_update_blacklist(1, $update_bl_cat);
+ if (squidguard_is_configured()) {
+ print "\nThe webproxy daemon must be restarted\n";
+ if ((defined($ENV{VYATTA_PROCESS_CLIENT}) && $ENV{VYATTA_PROCESS_CLIENT} eq 'gui2_rest') ||
+ prompt("Would you like to restart it now? [confirm]",-y1d=>"y")) {
+ squid_restart(1);
+ }
+ }
+ squidguard_clean_tmpfiles();
+}
+
+if (defined $update_bl) {
+ my $updated = 0;
+ if (!squidguard_is_blacklist_installed()) {
+ print "Warning: No url-filtering blacklist installed\n";
+ if ((defined($ENV{VYATTA_PROCESS_CLIENT}) && $ENV{VYATTA_PROCESS_CLIENT} eq 'gui2_rest') ||
+ prompt("Would you like to download a default blacklist? [confirm]",
+ -y1d=>"y")) {
+ exit 1 if squidguard_install_blacklist_def();
+ $updated = 1;
+ } else {
+ exit 1;
+ }
+ } else {
+ if ((defined($ENV{VYATTA_PROCESS_CLIENT}) && $ENV{VYATTA_PROCESS_CLIENT} eq 'gui2_rest') ||
+ prompt("Would you like to re-download the blacklist? [confirm]",
+ -y1d=>"y")) {
+ my $rc = squidguard_auto_update(1, undef);
+ $updated = 1 if ! $rc;
+ }
+ }
+ if (! $updated) {
+ print "No blacklist updated\n";
+ if ((defined($ENV{VYATTA_PROCESS_CLIENT}) && $ENV{VYATTA_PROCESS_CLIENT} eq 'gui2_rest') ||
+ !prompt("Do you still want to generate binary DB? [confirm]",
+ -y1d=>"y")) {
+ exit 1;
+ }
+ }
+ # if there was an update we need to re-gen the binary DBs
+ # and restart the daemon
+ squidguard_update_blacklist(1);
+ if (squidguard_is_configured()) {
+ print "\nThe webproxy daemon must be restarted\n";
+ if ((defined($ENV{VYATTA_PROCESS_CLIENT}) && $ENV{VYATTA_PROCESS_CLIENT} eq 'gui2_rest') ||
+ prompt("Would you like to restart it now? [confirm]",-y1d=>"y")) {
+ squid_restart(1);
+ }
+ }
+ squidguard_clean_tmpfiles();
+}
+
+if (defined $update_bl_file) {
+ if (! -e $update_bl_file) {
+ die "Error: file [$update_bl_file] doesn't exist";
+ }
+ my $rc = squidguard_auto_update(0, $update_bl_file);
+ exit 1 if $rc;
+ squidguard_update_blacklist(1);
+ squidguard_clean_tmpfiles();
+}
+
+if (defined $auto_update_bl) {
+ my $rc = squidguard_auto_update(0);
+ exit 1 if $rc;
+ squidguard_update_blacklist(0);
+ if (squidguard_is_configured()) {
+ squid_restart(0);
+ }
+ squidguard_clean_tmpfiles();
+}
+
+system("echo update succeeded at `date` > $sg_updatestatus_file");
+close($lck);
+exit 0;
+
+#end of file
diff --git a/src/op_mode/webproxy_update_blacklist.py b/src/op_mode/webproxy_update_blacklist.py
new file mode 100755
index 000000000..c6572c663
--- /dev/null
+++ b/src/op_mode/webproxy_update_blacklist.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# 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/>.
+
+#blacklist_url = 'ftp://ftp.univ-tlse1.fr/pub/reseau/cache/squidguard_contrib/blacklists.tar.gz'
+blacklist_url = 'http://lnx01.mybll.net/~cpo/blacklists.tar.gz'
+global_data_dir = '/config/url-filtering'
+sg_dir = f'{global_data_dir}/squidguard'
+blacklist_dir = f'{sg_dir}/db'
+archive_dir = f'{sg_dir}/archive'
+target_file = '/tmp/blacklists.tar.gz'
+
+#
+# XXX: this is a proof of concept for downloading a file via Python
+#
+
+
+import os
+import shutil
+import argparse
+import urllib.request
+import tarfile
+
+from tqdm import tqdm
+from vyos.util import chown
+from vyos.util import chmod
+
+parser = argparse.ArgumentParser()
+parser.add_argument("--update", help="Update SquidGuard blacklist",
+ action="store_true")
+args = parser.parse_args()
+
+class DownloadProgressBar(tqdm):
+ def update_to(self, b=1, bsize=1, tsize=None):
+ if tsize is not None:
+ self.total = tsize
+ self.update(b * bsize - self.n)
+
+def download_url(url, output_path):
+ with DownloadProgressBar(unit='B', unit_scale=True,
+ miniters=1, desc=url.split('/')[-1]) as t:
+ urllib.request.urlretrieve(url, filename=output_path, reporthook=t.update_to)
+
+def squidguard_is_blacklist_installed():
+ return os.path.exists(blacklist_dir)
+
+
+def install_blacklist():
+ download_url(blacklist_url, target_file)
+
+ print('Uncompressing blacklist...')
+ tar = tarfile.open(target_file, "r:gz")
+ tar.extractall(path='/tmp')
+ tar.close()
+
+ if not os.path.exists(sg_dir):
+ os.makedirs(sg_dir, exist_ok=True)
+
+ if os.path.exists(archive_dir):
+ print('Removing old archive...')
+ shutil.rmtree(archive_dir)
+
+ if os.path.exists(blacklist_dir):
+ print('Archiving old blacklist...')
+ shutil.move(blacklist_dir, archive_dir)
+
+ shutil.move('/tmp/blacklists', blacklist_dir)
+
+ chown(blacklist_dir, 'proxy', 'proxy')
+ chmod(blacklist_dir, 0o755)
+
+
+if args.update:
+ if not squidguard_is_blacklist_installed():
+ print('Warning: No url-filtering blacklist installed')
+ input('Would you like to download a default blacklist? [confirm]')
+
+ else:
+ input('Would you like to re-download the blacklist? [confirm]')
+
+ install_blacklist()