comparison sat/plugins/plugin_misc_ip.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents 003b8b4b56a7
children b64dd7c1496d
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SAT plugin for IP address discovery 4 # SAT plugin for IP address discovery
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 6
15 # GNU Affero General Public License for more details. 15 # GNU Affero General Public License for more details.
16 16
17 # You should have received a copy of the GNU Affero General Public License 17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 import urllib.parse
20 from sat.core.i18n import _, D_ 21 from sat.core.i18n import _, D_
21 from sat.core.constants import Const as C 22 from sat.core.constants import Const as C
22 from sat.core.log import getLogger 23 from sat.core.log import getLogger
23
24 log = getLogger(__name__)
25 from sat.tools import xml_tools 24 from sat.tools import xml_tools
25 from wokkel import disco, iwokkel
26 from twisted.web import client as webclient 26 from twisted.web import client as webclient
27 from twisted.web import error as web_error 27 from twisted.web import error as web_error
28 from twisted.internet import defer 28 from twisted.internet import defer
29 from twisted.internet import reactor 29 from twisted.internet import reactor
30 from twisted.internet import protocol 30 from twisted.internet import protocol
31 from twisted.internet import endpoints 31 from twisted.internet import endpoints
32 from twisted.internet import error as internet_error 32 from twisted.internet import error as internet_error
33 from zope.interface import implements 33 from zope.interface import implementer
34 from wokkel import disco, iwokkel
35 from twisted.words.protocols.jabber.xmlstream import XMPPHandler 34 from twisted.words.protocols.jabber.xmlstream import XMPPHandler
36 from twisted.words.protocols.jabber.error import StanzaError 35 from twisted.words.protocols.jabber.error import StanzaError
37 import urlparse 36
37 log = getLogger(__name__)
38 38
39 try: 39 try:
40 import netifaces 40 import netifaces
41 except ImportError: 41 except ImportError:
42 log.warning( 42 log.warning(
43 u"netifaces is not available, it help discovering IPs, you can install it on https://pypi.python.org/pypi/netifaces" 43 "netifaces is not available, it help discovering IPs, you can install it on https://pypi.python.org/pypi/netifaces"
44 ) 44 )
45 netifaces = None 45 netifaces = None
46 46
47 47
48 PLUGIN_INFO = { 48 PLUGIN_INFO = {
59 59
60 # TODO: GET_IP_PAGE should be configurable in sat.conf 60 # TODO: GET_IP_PAGE should be configurable in sat.conf
61 GET_IP_PAGE = ( 61 GET_IP_PAGE = (
62 "http://salut-a-toi.org/whereami/" 62 "http://salut-a-toi.org/whereami/"
63 ) # This page must only return external IP of the requester 63 ) # This page must only return external IP of the requester
64 GET_IP_LABEL = D_(u"Allow external get IP") 64 GET_IP_LABEL = D_("Allow external get IP")
65 GET_IP_CATEGORY = "General" 65 GET_IP_CATEGORY = "General"
66 GET_IP_NAME = "allow_get_ip" 66 GET_IP_NAME = "allow_get_ip"
67 GET_IP_CONFIRM_TITLE = D_(u"Confirm external site request") 67 GET_IP_CONFIRM_TITLE = D_("Confirm external site request")
68 GET_IP_CONFIRM = D_( 68 GET_IP_CONFIRM = D_(
69 u"""To facilitate data transfer, we need to contact a website. 69 """To facilitate data transfer, we need to contact a website.
70 A request will be done on {page} 70 A request will be done on {page}
71 That means that administrators of {domain} can know that you use "{app_name}" and your IP Address. 71 That means that administrators of {domain} can know that you use "{app_name}" and your IP Address.
72 72
73 IP address is an identifier to locate you on Internet (similar to a phone number). 73 IP address is an identifier to locate you on Internet (similar to a phone number).
74 74
75 Do you agree to do this request ? 75 Do you agree to do this request ?
76 """ 76 """
77 ).format( 77 ).format(
78 page=GET_IP_PAGE, domain=urlparse.urlparse(GET_IP_PAGE).netloc, app_name=C.APP_NAME 78 page=GET_IP_PAGE, domain=urllib.parse.urlparse(GET_IP_PAGE).netloc, app_name=C.APP_NAME
79 ) 79 )
80 NS_IP_CHECK = "urn:xmpp:sic:1" 80 NS_IP_CHECK = "urn:xmpp:sic:1"
81 81
82 PARAMS = """ 82 PARAMS = """
83 <params> 83 <params>
103 103
104 # NAT-Port 104 # NAT-Port
105 try: 105 try:
106 self._nat = host.plugins["NAT-PORT"] 106 self._nat = host.plugins["NAT-PORT"]
107 except KeyError: 107 except KeyError:
108 log.debug(u"NAT port plugin not available") 108 log.debug("NAT port plugin not available")
109 self._nat = None 109 self._nat = None
110 110
111 # XXX: cache is kept until SàT is restarted 111 # XXX: cache is kept until SàT is restarted
112 # if IP may have changed, use self.refreshIP 112 # if IP may have changed, use self.refreshIP
113 self._external_ip_cache = None 113 self._external_ip_cache = None
178 """Get local IP by doing a connection on an external url 178 """Get local IP by doing a connection on an external url
179 179
180 @param ext_utl(str): url to connect to 180 @param ext_utl(str): url to connect to
181 @return (D(str)): return local IP 181 @return (D(str)): return local IP
182 """ 182 """
183 url = urlparse.urlparse(ext_url) 183 url = urllib.parse.urlparse(ext_url)
184 port = url.port 184 port = url.port
185 if port is None: 185 if port is None:
186 if url.scheme == "http": 186 if url.scheme == "http":
187 port = 80 187 port = 80
188 elif url.scheme == "https": 188 elif url.scheme == "https":
189 port = 443 189 port = 443
190 else: 190 else:
191 log.error(u"Unknown url scheme: {}".format(url.scheme)) 191 log.error("Unknown url scheme: {}".format(url.scheme))
192 defer.returnValue(None) 192 defer.returnValue(None)
193 if url.hostname is None: 193 if url.hostname is None:
194 log.error(u"Can't find url hostname for {}".format(GET_IP_PAGE)) 194 log.error("Can't find url hostname for {}".format(GET_IP_PAGE))
195 195
196 point = endpoints.TCP4ClientEndpoint(reactor, url.hostname, port) 196 point = endpoints.TCP4ClientEndpoint(reactor, url.hostname, port)
197 197
198 def gotConnection(p): 198 def gotConnection(p):
199 local_ip = p.transport.getHost().host 199 local_ip = p.transport.getHost().host
255 defer.returnValue(addresses or localhost) 255 defer.returnValue(addresses or localhost)
256 256
257 try: 257 try:
258 ip_tuple = yield self._getIPFromExternal(GET_IP_PAGE) 258 ip_tuple = yield self._getIPFromExternal(GET_IP_PAGE)
259 except (internet_error.DNSLookupError, internet_error.TimeoutError): 259 except (internet_error.DNSLookupError, internet_error.TimeoutError):
260 log.warning(u"Can't access Domain Name System") 260 log.warning("Can't access Domain Name System")
261 defer.returnValue(addresses or localhost) 261 defer.returnValue(addresses or localhost)
262 self._insertFirst(addresses, ip_tuple.local) 262 self._insertFirst(addresses, ip_tuple.local)
263 defer.returnValue(addresses) 263 defer.returnValue(addresses)
264 264
265 @defer.inlineCallbacks 265 @defer.inlineCallbacks
272 defer.returnValue(self._external_ip_cache) 272 defer.returnValue(self._external_ip_cache)
273 273
274 # we first try with XEP-0279 274 # we first try with XEP-0279
275 ip_check = yield self.host.hasFeature(client, NS_IP_CHECK) 275 ip_check = yield self.host.hasFeature(client, NS_IP_CHECK)
276 if ip_check: 276 if ip_check:
277 log.debug(u"Server IP Check available, we use it to retrieve our IP") 277 log.debug("Server IP Check available, we use it to retrieve our IP")
278 iq_elt = client.IQ("get") 278 iq_elt = client.IQ("get")
279 iq_elt['to'] = client.host
279 iq_elt.addElement((NS_IP_CHECK, "address")) 280 iq_elt.addElement((NS_IP_CHECK, "address"))
280 try: 281 try:
281 result_elt = yield iq_elt.send() 282 result_elt = yield iq_elt.send()
282 address_elt = result_elt.elements(NS_IP_CHECK, "address").next() 283 address_elt = next(result_elt.elements(NS_IP_CHECK, "address"))
283 ip_elt = address_elt.elements(NS_IP_CHECK, "ip").next() 284 ip_elt = next(address_elt.elements(NS_IP_CHECK, "ip"))
284 except StopIteration: 285 except StopIteration:
285 log.warning( 286 log.warning(
286 u"Server returned invalid result on XEP-0279 request, we ignore it" 287 "Server returned invalid result on XEP-0279 request, we ignore it"
287 ) 288 )
288 except StanzaError as e: 289 except StanzaError as e:
289 log.warning(u"error while requesting ip to server: {}".format(e)) 290 log.warning("error while requesting ip to server: {}".format(e))
290 else: 291 else:
291 # FIXME: server IP may not be the same as external IP (server can be on local machine or network) 292 # FIXME: server IP may not be the same as external IP (server can be on local machine or network)
292 # IP should be checked to see if we have a local one, and rejected in this case 293 # IP should be checked to see if we have a local one, and rejected in this case
293 external_ip = str(ip_elt) 294 external_ip = str(ip_elt)
294 log.debug(u"External IP found: {}".format(external_ip)) 295 log.debug("External IP found: {}".format(external_ip))
295 self._external_ip_cache = external_ip 296 self._external_ip_cache = external_ip
296 defer.returnValue(self._external_ip_cache) 297 defer.returnValue(self._external_ip_cache)
297 298
298 # then with NAT-Port 299 # then with NAT-Port
299 if self._nat is not None: 300 if self._nat is not None:
303 defer.returnValue(nat_ip) 304 defer.returnValue(nat_ip)
304 305
305 # and finally by requesting external website 306 # and finally by requesting external website
306 allow_get_ip = yield self._externalAllowed(client) 307 allow_get_ip = yield self._externalAllowed(client)
307 try: 308 try:
308 ip = (yield webclient.getPage(GET_IP_PAGE)) if allow_get_ip else None 309 ip = ((yield webclient.getPage(GET_IP_PAGE.encode('utf-8')))
310 if allow_get_ip else None)
309 except (internet_error.DNSLookupError, internet_error.TimeoutError): 311 except (internet_error.DNSLookupError, internet_error.TimeoutError):
310 log.warning(u"Can't access Domain Name System") 312 log.warning("Can't access Domain Name System")
311 ip = None 313 ip = None
312 except web_error.Error as e: 314 except web_error.Error as e:
313 log.warning( 315 log.warning(
314 u"Error while retrieving IP on {url}: {message}".format( 316 "Error while retrieving IP on {url}: {message}".format(
315 url=GET_IP_PAGE, message=e 317 url=GET_IP_PAGE, message=e
316 ) 318 )
317 ) 319 )
318 ip = None 320 ip = None
319 else: 321 else:
320 self._external_ip_cache = ip 322 self._external_ip_cache = ip
321 defer.returnValue(ip) 323 defer.returnValue(ip)
322 324
323 325
326 @implementer(iwokkel.IDisco)
324 class IPPlugin_handler(XMPPHandler): 327 class IPPlugin_handler(XMPPHandler):
325 implements(iwokkel.IDisco)
326 328
327 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): 329 def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
328 return [disco.DiscoFeature(NS_IP_CHECK)] 330 return [disco.DiscoFeature(NS_IP_CHECK)]
329 331
330 def getDiscoItems(self, requestor, target, nodeIdentifier=""): 332 def getDiscoItems(self, requestor, target, nodeIdentifier=""):