From 1582681f7ab7a8fc58eb9e6fc508f7c69a5120df Mon Sep 17 00:00:00 2001
From: zsdc <taras@sentrium.io>
Date: Tue, 8 Oct 2019 19:49:36 +0300
Subject: [BGP] T1490: Added migration for obsoleted 'bgp scan-time' parameter

---
 src/migration-scripts/quagga/3-to-4 | 63 +++++++++++++++++++++++++++++++++++++
 1 file changed, 63 insertions(+)
 create mode 100644 src/migration-scripts/quagga/3-to-4

(limited to 'src')

diff --git a/src/migration-scripts/quagga/3-to-4 b/src/migration-scripts/quagga/3-to-4
new file mode 100644
index 000000000..f8c87ce8c
--- /dev/null
+++ b/src/migration-scripts/quagga/3-to-4
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 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 sys
+
+from vyos.configtree import ConfigTree
+
+
+if (len(sys.argv) < 1):
+    print("Must specify file name!")
+    sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+    config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['protocols', 'bgp']):
+    # Nothing to do
+    sys.exit(0)
+else:
+    # Check if BGP is actually configured and obtain the ASN
+    asn_list = config.list_nodes(['protocols', 'bgp'])
+    if asn_list:
+        # There's always just one BGP node, if any
+        asn = asn_list[0]
+    else:
+        # There's actually no BGP, just its empty shell
+        sys.exit(0)
+
+    # Check if BGP scan-time parameter exist
+    scan_time_param = ['protocols', 'bgp', asn, 'parameters', 'scan-time']
+    if config.exists(scan_time_param):
+        # Delete BGP scan-time parameter
+        config.delete(scan_time_param)
+    else:
+        # Do nothing
+        sys.exit(0)
+
+    # Save a new configuration file
+    try:
+        with open(file_name, 'w') as f:
+            f.write(config.to_string())
+    except OSError as e:
+        print("Failed to save the modified config: {}".format(e))
+        sys.exit(1)
-- 
cgit v1.2.3


From 64163806044bed98fcb68b46340e767deb0c8acc Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Sun, 13 Oct 2019 18:07:23 +0200
Subject: ssh.py: check if file exists before deleting it

---
 src/conf_mode/ssh.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

(limited to 'src')

diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py
index 2a5cba99a..e3b11b537 100755
--- a/src/conf_mode/ssh.py
+++ b/src/conf_mode/ssh.py
@@ -267,7 +267,8 @@ def apply(ssh):
     else:
         # SSH access is removed in the commit
         os.system("sudo systemctl stop ssh.service")
-        os.unlink(config_file)
+        if os.path.isfile(config_file):
+            os.unlink(config_file)
 
     return None
 
-- 
cgit v1.2.3


From 00adf5c48f4c29884d56d85c9ad24a2ca5b82444 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Sun, 13 Oct 2019 19:15:40 +0200
Subject: openvpn: T1548: clean out import statements

---
 src/conf_mode/interfaces-openvpn.py | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

(limited to 'src')

diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 5345bf7a2..cdd133904 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -16,11 +16,11 @@
 
 import os
 import re
-import sys
-import stat
-import jinja2
 
+from jinja2 import Template
 from copy import deepcopy
+from sys import exit
+from stat import S_IRUSR,S_IRWXU,S_IRGRP,S_IXGRP,S_IROTH,S_IXOTH
 from grp import getgrnam
 from ipaddress import ip_address,ip_network,IPv4Interface
 from netifaces import interfaces
@@ -331,12 +331,12 @@ def openvpn_mkdir(directory):
         os.mkdir(directory)
 
     # fix permissions - corresponds to mode 755
-    os.chmod(directory, stat.S_IRWXU|stat.S_IRGRP|stat.S_IXGRP|stat.S_IROTH|stat.S_IXOTH)
+    os.chmod(directory, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
     uid = getpwnam(user).pw_uid
     gid = getgrnam(group).gr_gid
     os.chown(directory, uid, gid)
 
-def fixup_permission(filename, permission=stat.S_IRUSR):
+def fixup_permission(filename, permission=S_IRUSR):
     """
     Check if the given file exists and change ownershit to root/vyattacfg
     and appripriate file access permissions - default is user and group readable
@@ -737,7 +737,7 @@ def verify(openvpn):
     if openvpn['shared_secret_file']:
         if openvpn['encryption'] in ['aes128gcm', 'aes192gcm', 'aes256gcm']:
             raise ConfigError('GCM encryption with shared-secret-key-file is not supported')
-        
+
         if not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', openvpn['shared_secret_file']):
             raise ConfigError('Specified shared-secret-key-file "{}" is not valid'.format(openvpn['shared_secret_file']))
 
@@ -851,13 +851,13 @@ def generate(openvpn):
     # Generate client specific configuration
     for client in openvpn['client']:
         client_file = directory + '/ccd/' + interface + '/' + client['name']
-        tmpl = jinja2.Template(client_tmpl)
+        tmpl = Template(client_tmpl)
         client_text = tmpl.render(client)
         with open(client_file, 'w') as f:
             f.write(client_text)
         os.chown(client_file, uid, gid)
 
-    tmpl = jinja2.Template(config_tmpl)
+    tmpl = Template(config_tmpl)
     config_text = tmpl.render(openvpn)
 
     # we need to support quoting of raw parameters from OpenVPN CLI
@@ -957,4 +957,4 @@ if __name__ == '__main__':
         apply(c)
     except ConfigError as e:
         print(e)
-        sys.exit(1)
+        exit(1)
-- 
cgit v1.2.3


From 069f7e5a8431f95d7316a0da682e962f6eb1320a Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Sun, 13 Oct 2019 19:55:25 +0200
Subject: Revert "snmpd: T1705 - High CPU usage by bgpd when snmp is active"

Systems not runing BGP won't boot anymore. Syslog shows:

  snmpd[5404]: getaddrinfo: inetCidrRouteTable Name or service not known
  snmpd[5404]: getaddrinfo("inetCidrRouteTable", NULL, ...): Name or service not known
  snmpd[5404]: Error opening specified endpoint "inetCidrRouteTable"
  snmpd[5404]: Server Exiting with code 1
  snmpd[5401]: Starting SNMP services::
  systemd[1]: snmpd.service: control process exited, code=exited status=1
  systemd[1]: Failed to start LSB: SNMP agents.
  systemd[1]: Unit snmpd.service entered failed state.

This reverts commit e45648cdd5a52569be7f3ac30473b0c7474a7894.
---
 src/conf_mode/snmp.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src')

diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 60e4c343d..0ddab2129 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -224,7 +224,7 @@ init_config_tmpl = """
 SNMPDRUN=yes
 
 # snmpd options (use syslog, close stdin/out/err).
-SNMPDOPTS='-LSed -u snmp -g snmp -I -ipCidrRouteTable, inetCidrRouteTable -p /run/snmpd.pid'
+SNMPDOPTS='-LSed -u snmp -g snmp -p /run/snmpd.pid'
 """
 
 default_config_data = {
-- 
cgit v1.2.3


From 50acd442ade9a4e447269eaf94ce14d354af8d0c Mon Sep 17 00:00:00 2001
From: hagbard <vyosdev@derith.de>
Date: Tue, 15 Oct 2019 08:53:45 -0700
Subject: snmpd: T1705 - High CPU usage by bgpd when snmp is active

  * typo fixed
---
 src/conf_mode/snmp.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src')

diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 0ddab2129..cba1fe319 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -224,7 +224,7 @@ init_config_tmpl = """
 SNMPDRUN=yes
 
 # snmpd options (use syslog, close stdin/out/err).
-SNMPDOPTS='-LSed -u snmp -g snmp -p /run/snmpd.pid'
+SNMPDOPTS='-LSed -u snmp -g snmp -I -ipCidrRouteTable,inetCidrRouteTable -p /run/snmpd.pid'
 """
 
 default_config_data = {
-- 
cgit v1.2.3


From e4424dce0d387c5f9e9a710369cd5136fcd63c77 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Thu, 17 Oct 2019 22:05:12 +0200
Subject: bgp: T1490: fix migrator file permissions

---
 src/migration-scripts/quagga/3-to-4 | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 mode change 100644 => 100755 src/migration-scripts/quagga/3-to-4

(limited to 'src')

diff --git a/src/migration-scripts/quagga/3-to-4 b/src/migration-scripts/quagga/3-to-4
old mode 100644
new mode 100755
-- 
cgit v1.2.3


From df9544233fb661e830285c1a0d7755cff4b27408 Mon Sep 17 00:00:00 2001
From: hagbard <vyosdev@derith.de>
Date: Fri, 18 Oct 2019 10:58:03 -0700
Subject: system-proxy: T1741 - Add system wide proxy setting CLI
 implementation

---
 interface-definitions/system-proxy.xml | 43 ++++++++++++++++
 src/conf_mode/system-proxy.py          | 91 ++++++++++++++++++++++++++++++++++
 2 files changed, 134 insertions(+)
 create mode 100644 interface-definitions/system-proxy.xml
 create mode 100755 src/conf_mode/system-proxy.py

(limited to 'src')

diff --git a/interface-definitions/system-proxy.xml b/interface-definitions/system-proxy.xml
new file mode 100644
index 000000000..f43702fc8
--- /dev/null
+++ b/interface-definitions/system-proxy.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+  <node name="system">
+    <children>
+      <node name="proxy" owner="${vyos_conf_scripts_dir}/system-proxy.py">
+        <properties>
+          <help>Sets a proxy for system wide use</help>
+        </properties>
+        <children>
+          <leafNode name="url">
+            <properties>
+              <help>Proxy URL</help>
+              <constraint>
+                <regex>^http://[a-z0-9\.]+$</regex>
+              </constraint>
+            </properties>
+          </leafNode>
+          <leafNode name="port">
+            <properties>
+              <help>Proxy port</help>
+              <constraint>
+                <validator name="numeric" argument="--range 1-65535"/>
+              </constraint>
+            </properties>
+          </leafNode>
+          <leafNode name="username">
+            <properties>
+              <help>Proxy username</help>
+              <constraint>
+                <regex>^[a-z0-9-_\.]{1,100}$</regex>
+              </constraint>
+            </properties>
+          </leafNode>
+          <leafNode name="password">
+            <properties>
+              <help>Proxy password</help>
+            </properties>
+          </leafNode>
+        </children>
+      </node>
+    </children>
+  </node>
+</interfaceDefinition>
diff --git a/src/conf_mode/system-proxy.py b/src/conf_mode/system-proxy.py
new file mode 100755
index 000000000..02e1a2eda
--- /dev/null
+++ b/src/conf_mode/system-proxy.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 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 sys
+import os
+import re
+import subprocess
+
+from vyos import ConfigError
+from vyos.config import Config
+
+proxy_def=r'/etc/profile.d/vyos-system-proxy.sh'
+
+def get_config():
+  c = Config()
+  if not c.exists('system proxy'):
+    return None
+
+  c.set_level('system proxy')
+
+  cnf = {
+    'url'     : None,
+    'port'    : None,
+    'usr'     : None,
+    'passwd'  : None
+  }
+
+  if c.exists('url'):
+    cnf['url'] = c.return_value('url')
+  if c.exists('port'):
+    cnf['port'] = c.return_value('port')
+  if c.exists('username'):
+    cnf['usr'] = c.return_value('username')
+  if c.exists('password'):
+    cnf['passwd'] = c.return_value('password')
+
+  return cnf
+
+def verify(c):
+  if not c:
+    return None 
+  if not c['url'] or not c['port']:
+    raise ConfigError("proxy url and port requires a value")
+  elif c['usr'] and not c['passwd']:
+    raise ConfigError("proxy password requires a value")
+  elif not c['usr'] and c['passwd']:
+    raise ConfigError("proxy username requires a value")
+
+def generate(c):
+  if not c:
+    return None
+  if not c['usr']:
+    return str("export http_proxy={url}:{port}\nexport https_proxy=$http_proxy\nexport ftp_proxy=$http_proxy"\
+              .format(url=c['url'], port=c['port']))
+  else:
+    return str("export http_proxy=http://{usr}:{passwd}@{url}:{port}\nexport https_proxy=$http_proxy\nexport ftp_proxy=$http_proxy"\
+              .format(url=re.sub('http://','',c['url']), port=c['port'], usr=c['usr'], passwd=c['passwd']))
+
+def apply(ln):
+  if not ln and os.path.exists(proxy_def):
+    subprocess.call(['unset http_proxy https_proxy ftp_proxy'], shell=True)
+    os.remove(proxy_def)
+  else:
+    open(proxy_def,'w').write("# generated by system-proxy.py\n{}\n".format(ln))
+    subprocess.call(['. /etc/profile.d/vyos-system-proxy.sh'], shell=True)
+
+if __name__ == '__main__':
+    try:
+        c = get_config()
+        verify(c)
+        ln = generate(c)
+        apply(ln)
+    except ConfigError as e:
+        print(e)
+        sys.exit(1)
+
-- 
cgit v1.2.3


From d2aa68e5e6a16e5e02ca7301ad9c37cbccbff83e Mon Sep 17 00:00:00 2001
From: hagbard <vyosdev@derith.de>
Date: Fri, 18 Oct 2019 11:43:10 -0700
Subject: wireguard - remove endpoint check to enable roaming connections

---
 src/conf_mode/interfaces-wireguard.py | 3 ---
 1 file changed, 3 deletions(-)

(limited to 'src')

diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index 7a684bafa..013a07f32 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -190,9 +190,6 @@ def verify(c):
                 raise ConfigError("ERROR: allowed-ips required for peer " + p)
             if not c['peer'][p]['pubkey']:
                 raise ConfigError("peer pubkey required for peer " + p)
-            if not c['peer'][p]['endpoint']:
-                raise ConfigError("peer endpoint required for peer " + p)
-
 
 def apply(c):
     # no wg configs left, remove all interface from system
-- 
cgit v1.2.3


From f87b5fa3b9a2c1c6826701924712654c8f0c5709 Mon Sep 17 00:00:00 2001
From: hagbard <vyosdev@derith.de>
Date: Fri, 18 Oct 2019 12:37:19 -0700
Subject: system-proxy: T1741 - Add system wide proxy setting

  * removed subprocess as it is not required, script is executed via sudo
  * pep8 formatted
---
 src/conf_mode/system-proxy.py | 97 ++++++++++++++++++++++---------------------
 1 file changed, 49 insertions(+), 48 deletions(-)

(limited to 'src')

diff --git a/src/conf_mode/system-proxy.py b/src/conf_mode/system-proxy.py
index 02e1a2eda..cf72a1f96 100755
--- a/src/conf_mode/system-proxy.py
+++ b/src/conf_mode/system-proxy.py
@@ -19,65 +19,67 @@
 import sys
 import os
 import re
-import subprocess
 
 from vyos import ConfigError
 from vyos.config import Config
 
-proxy_def=r'/etc/profile.d/vyos-system-proxy.sh'
+proxy_def = r'/etc/profile.d/vyos-system-proxy.sh'
+
 
 def get_config():
-  c = Config()
-  if not c.exists('system proxy'):
-    return None
-
-  c.set_level('system proxy')
-
-  cnf = {
-    'url'     : None,
-    'port'    : None,
-    'usr'     : None,
-    'passwd'  : None
-  }
-
-  if c.exists('url'):
-    cnf['url'] = c.return_value('url')
-  if c.exists('port'):
-    cnf['port'] = c.return_value('port')
-  if c.exists('username'):
-    cnf['usr'] = c.return_value('username')
-  if c.exists('password'):
-    cnf['passwd'] = c.return_value('password')
-
-  return cnf
+    c = Config()
+    if not c.exists('system proxy'):
+        return None
+
+    c.set_level('system proxy')
+
+    cnf = {
+        'url': None,
+      'port': None,
+      'usr': None,
+      'passwd': None
+    }
+
+    if c.exists('url'):
+        cnf['url'] = c.return_value('url')
+    if c.exists('port'):
+        cnf['port'] = c.return_value('port')
+    if c.exists('username'):
+        cnf['usr'] = c.return_value('username')
+    if c.exists('password'):
+        cnf['passwd'] = c.return_value('password')
+
+    return cnf
+
 
 def verify(c):
-  if not c:
-    return None 
-  if not c['url'] or not c['port']:
-    raise ConfigError("proxy url and port requires a value")
-  elif c['usr'] and not c['passwd']:
-    raise ConfigError("proxy password requires a value")
-  elif not c['usr'] and c['passwd']:
-    raise ConfigError("proxy username requires a value")
+    if not c:
+        return None
+    if not c['url'] or not c['port']:
+        raise ConfigError("proxy url and port requires a value")
+    elif c['usr'] and not c['passwd']:
+        raise ConfigError("proxy password requires a value")
+    elif not c['usr'] and c['passwd']:
+        raise ConfigError("proxy username requires a value")
+
 
 def generate(c):
-  if not c:
-    return None
-  if not c['usr']:
-    return str("export http_proxy={url}:{port}\nexport https_proxy=$http_proxy\nexport ftp_proxy=$http_proxy"\
-              .format(url=c['url'], port=c['port']))
-  else:
-    return str("export http_proxy=http://{usr}:{passwd}@{url}:{port}\nexport https_proxy=$http_proxy\nexport ftp_proxy=$http_proxy"\
-              .format(url=re.sub('http://','',c['url']), port=c['port'], usr=c['usr'], passwd=c['passwd']))
+    if not c:
+        return None
+    if not c['usr']:
+        return str("export http_proxy={url}:{port}\nexport https_proxy=$http_proxy\nexport ftp_proxy=$http_proxy"
+                   .format(url=c['url'], port=c['port']))
+    else:
+        return str("export http_proxy=http://{usr}:{passwd}@{url}:{port}\nexport https_proxy=$http_proxy\nexport ftp_proxy=$http_proxy"
+                   .format(url=re.sub('http://', '', c['url']), port=c['port'], usr=c['usr'], passwd=c['passwd']))
+
 
 def apply(ln):
-  if not ln and os.path.exists(proxy_def):
-    subprocess.call(['unset http_proxy https_proxy ftp_proxy'], shell=True)
-    os.remove(proxy_def)
-  else:
-    open(proxy_def,'w').write("# generated by system-proxy.py\n{}\n".format(ln))
-    subprocess.call(['. /etc/profile.d/vyos-system-proxy.sh'], shell=True)
+    if not ln and os.path.exists(proxy_def):
+        os.remove(proxy_def)
+    else:
+        open(proxy_def, 'w').write(
+            "# generated by system-proxy.py\n{}\n".format(ln))
 
 if __name__ == '__main__':
     try:
@@ -88,4 +90,3 @@ if __name__ == '__main__':
     except ConfigError as e:
         print(e)
         sys.exit(1)
-
-- 
cgit v1.2.3


From 735d73e162634d598aa6b8ee13197aa231eefedb Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Sat, 19 Oct 2019 10:11:46 +0200
Subject: dhcp-server: T1745: bugfix corner case on static-assignments

There was a bug when refactoring this with commits 5848a4d ("dhcp-server:
T1707: remove DHCP static-mappings from address pool") and 1182b44
("dhcp-server: T1707: bugfix on subsequent DHCP exclude addresses") that when
a static address assignemnt was using the last IP address from the specified
range.

This triggered the following error:
  "DHCP range stop address x must be greater or equal to the range start
   address y!"
---
 src/conf_mode/dhcp_server.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

(limited to 'src')

diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py
index f19bcb250..af803a696 100755
--- a/src/conf_mode/dhcp_server.py
+++ b/src/conf_mode/dhcp_server.py
@@ -303,7 +303,8 @@ def dhcp_slice_range(exclude_list, range_list):
                       'start': str(ip_address(e) + 1),
                       'stop': str(range_stop)
                     }
-                    output.append(r)
+                    if not (ip_address(r['start']) > ip_address(r['stop'])):
+                        output.append(r)
             else:
               # if we have no exclude in the whole range - we just take the range
               # as it is
-- 
cgit v1.2.3


From 6f73338f0a652ca9b68a5778456f63d098f04522 Mon Sep 17 00:00:00 2001
From: Daniil Baturin <daniil@baturin.org>
Date: Sat, 19 Oct 2019 10:28:00 +0200
Subject: T1749: support multiple ranges in the numeric validator.

---
 src/validators/numeric | 28 +++++++++++++++++++---------
 1 file changed, 19 insertions(+), 9 deletions(-)

(limited to 'src')

diff --git a/src/validators/numeric b/src/validators/numeric
index ffe84a234..0a2d83d14 100755
--- a/src/validators/numeric
+++ b/src/validators/numeric
@@ -24,7 +24,7 @@ import re
 parser = argparse.ArgumentParser()
 parser.add_argument("-f", "--float", action="store_true", help="Accept floating point values")
 group = parser.add_mutually_exclusive_group()
-group.add_argument("-r", "--range", type=str, help="Check if the number is within range (inclusive), example: 1024-65535")
+group.add_argument("-r", "--range", type=str, help="Check if the number is within range (inclusive), example: 1024-65535", action='append')
 group.add_argument("-n", "--non-negative", action="store_true", help="Check if the number is non-negative (>= 0)")
 group.add_argument("-p", "--positive", action="store_true", help="Check if the number is positive (> 0)")
 parser.add_argument("number", type=str, help="Number to validate")
@@ -47,15 +47,25 @@ else:
         sys.exit(1)
 
 if args.range:
-    try:
-        lower, upper = re.match(r'(\d+)\s*\-\s*(\d+)', args.range).groups()
-        lower, upper = int(lower), int(upper)
-    except:
-        print("{0} is not a valid number range",format(args.range), file=sys.stderr)
-        sys.exit(1)
+    valid = False
+    for r in args.range:
+        try:
+            lower, upper = re.match(r'(\d+)\s*\-\s*(\d+)', r).groups()
+            lower, upper = int(lower), int(upper)
+        except:
+            print("{0} is not a valid number range",format(args.range), file=sys.stderr)
+            sys.exit(1)
+
+        if (number >= lower) and (number <= upper):
+            valid = True
+    # end for
 
-    if (number < lower) or (number > upper):
-        print("Number {0} is not in the {1} range".format(number, args.range), file=sys.stderr)
+    if not valid:
+        if len(args.range) > 1:
+            err_msg = "Number {0} is not in any of the ranges {1}".format(number, args.range)
+        else:
+            err_msg = "Number {0} is not in the range {1}".format(number, args.range[0])
+        print(err_msg, file=sys.stderr)
         sys.exit(1)
 elif args.non_negative:
     if number < 0:
-- 
cgit v1.2.3