diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py b/web/pgadmin/browser/server_groups/servers/__init__.py index 5d7895652..1a9eb6a8f 100644 --- a/web/pgadmin/browser/server_groups/servers/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/__init__.py @@ -8,7 +8,6 @@ ########################################################################## import simplejson as json -import re import pgadmin.browser.server_groups as sg from flask import render_template, request, make_response, jsonify, \ current_app, url_for @@ -20,6 +19,7 @@ from pgadmin.utils.ajax import make_json_response, bad_request, forbidden, \ make_response as ajax_response, internal_server_error, unauthorized, gone from pgadmin.utils.crypto import encrypt, decrypt, pqencryptpassword from pgadmin.utils.menu import MenuItem +from pgadmin.utils.ip import is_valid_ipaddress from pgadmin.tools.sqleditor.utils.query_history import QueryHistory import config @@ -272,31 +272,6 @@ class ServerNode(PGChildNodeView): 'clear_saved_password': [{'put': 'clear_saved_password'}], 'clear_sshtunnel_password': [{'put': 'clear_sshtunnel_password'}] }) - EXP_IP4 = "^\s*((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\." \ - "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\." \ - "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\." \ - "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\s*$" - EXP_IP6 = '^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|' \ - '(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|' \ - '2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d))' \ - '{3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|' \ - ':((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d' \ - '|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]' \ - '{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|' \ - '[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|' \ - '(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-' \ - 'Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25' \ - '[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:)' \ - '{2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:(' \ - '(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|' \ - '[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]' \ - '{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|'\ - '1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' \ - '|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((' \ - '25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|' \ - '[1-9]?\d)){3}))|:)))(%.+)?\s*$' - pat4 = re.compile(EXP_IP4) - pat6 = re.compile(EXP_IP6) SSL_MODES = ['prefer', 'require', 'verify-ca', 'verify-full'] def check_ssl_fields(self, data): @@ -570,7 +545,7 @@ class ServerNode(PGChildNodeView): ): if arg in data: return forbidden( - errormsg=gettext( + errmsg=gettext( "'{0}' is not allowed to modify, " "when server is connected." ).format(disp_lbl[arg]) @@ -771,13 +746,12 @@ class ServerNode(PGChildNodeView): ) if 'hostaddr' in data and data['hostaddr'] and data['hostaddr'] != '': - if not self.pat4.match(data['hostaddr']): - if not self.pat6.match(data['hostaddr']): - return make_json_response( - success=0, - status=400, - errormsg=gettext('Host address not valid') - ) + if not is_valid_ipaddress(data['hostaddr']): + return make_json_response( + success=0, + status=400, + errormsg=gettext('Not a valid Host address') + ) # To check ssl configuration is_ssl, data = self.check_ssl_fields(data) diff --git a/web/pgadmin/utils/ip.py b/web/pgadmin/utils/ip.py new file mode 100644 index 000000000..9bc656131 --- /dev/null +++ b/web/pgadmin/utils/ip.py @@ -0,0 +1,61 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +######################################################################### + +"""This File Provides ipv4 and ipv6 address check.""" + +import re + +IPV4SEG = r'(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])' +IPV4ADDR = r'(?:(?:' + IPV4SEG + r'\.){3,3}' + IPV4SEG + r')' +IPV6SEG = r'(?:(?:[0-9a-fA-F]){1,4})' +IPV6GROUPS = ( + # 1:2:3:4:5:6:7:8 + r'(?:' + IPV6SEG + r':){7,7}' + IPV6SEG, + # 1:: 1:2:3:4:5:6:7:: + r'(?:' + IPV6SEG + r':){1,7}:', + # 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8 + r'(?:' + IPV6SEG + r':){1,6}:' + IPV6SEG, + # 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8 + r'(?:' + IPV6SEG + r':){1,5}(?::' + IPV6SEG + r'){1,2}', + # 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8 + r'(?:' + IPV6SEG + r':){1,4}(?::' + IPV6SEG + r'){1,3}', + # 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8 + r'(?:' + IPV6SEG + r':){1,3}(?::' + IPV6SEG + r'){1,4}', + # 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8 + r'(?:' + IPV6SEG + r':){1,2}(?::' + IPV6SEG + r'){1,5}', + # 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8 + IPV6SEG + r':(?:(?::' + IPV6SEG + r'){1,6})', + # ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 :: + r':(?:(?::' + IPV6SEG + r'){1,7}|:)', + # fe80::7:8%eth0 fe80::7:8%1 (link-local IPv6 addresses with zone index) + r'fe80:(?::' + IPV6SEG + r'){0,4}%[0-9a-zA-Z]{1,}', + # ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 + # (IPv4-mapped IPv6 addresses and IPv4-translated addresses) + r'::(?:ffff(?::0{1,4}){0,1}:){0,1}[^\s:]' + IPV4ADDR, + # 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 + # (IPv4-Embedded IPv6 Address) + r'(?:' + IPV6SEG + r':){1,4}:[^\s:]' + IPV4ADDR, +) +# Reverse rows for greedy match +IPV6ADDR = '|'.join(['(?:{})'.format(g) for g in IPV6GROUPS[::-1]]) + +ip6re = re.compile(IPV6ADDR) +ip4re = re.compile(IPV4ADDR) + + +def is_valid_ip4address(addr): + return ip4re.match(addr) + + +def is_valid_ip6address(addr): + return ip6re.match(addr) + + +def is_valid_ipaddress(addr): + return ip4re.match(addr) and is_valid_ip6address(addr)