From a8cfaea81164d40c68bb8d868ff5e364e945e162 Mon Sep 17 00:00:00 2001
From: Romeo Papa <romeopapa@caramail.com>
Date: Wed, 12 Aug 2015 06:06:47 +0200
Subject: [PATCH 2/2] The SOCKS host and port can be configured in
 whisperback's config.py

---
 debian/control             |  4 ++--
 whisperBack/mail.py        | 11 +++++-----
 whisperBack/whisperback.py | 52 +++++++++++++++++++++++++++++++++++++++++-----
 3 files changed, 55 insertions(+), 12 deletions(-)

diff --git a/debian/control b/debian/control
index 877d6ad..f0ccf21 100644
--- a/debian/control
+++ b/debian/control
@@ -12,8 +12,8 @@ X-Python3-Version: >= 3.4
 
 Package: whisperback
 Architecture: all
-Depends: ${misc:Depends}, ${python3:Depends}, python3-gnupg, python3-gi,
- python3-pysocks, gir1.2-gtk-3.0, gir1.2-webkit-3.0, gir1.2-glib-2.0
+Depends: ${misc:Depends}, ${python3:Depends}, python3-regex, python3-gnupg,
+ python3-gi, python3-pysocks, gir1.2-gtk-3.0, gir1.2-webkit-3.0, gir1.2-glib-2.0
 Description: send feedback using encrypted email
  WhisperBack is designed to allow Live system users to be able to report bugs
  or issues in a simple yet secure manner.
diff --git a/whisperBack/mail.py b/whisperBack/mail.py
index 139b988..53c0be3 100644
--- a/whisperBack/mail.py
+++ b/whisperBack/mail.py
@@ -25,15 +25,16 @@
 
 """
 
+import os
 import smtplib
-import ssl
-import time
 import socket
 import socks
+import ssl
+import time
 
 #pylint: disable=R0913
-def send_message_tls (from_address, to_address, message, host="localhost",
-                      port=25, tls_cafile=None):
+def send_message_tls (from_address, to_address, message, socks_host,
+                      socks_port, host="localhost", port=25, tls_cafile=None):
     """Sends a mail
 
     Send the message via our own SMTP server, but don't include the
@@ -48,7 +49,7 @@ def send_message_tls (from_address, to_address, message, host="localhost",
     """
 
     # Monkeypatching the entire connection through the SOCKS proxy
-    socks.set_default_proxy(socks.SOCKS5, "127.0.0.1", 9050)
+    socks.set_default_proxy(socks.SOCKS5, socks_host, socks_port)
     socket.socket = socks.socksocket
 
     ssl_context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH,
diff --git a/whisperBack/whisperback.py b/whisperBack/whisperback.py
index 5f41dd9..30fa6c7 100644
--- a/whisperBack/whisperback.py
+++ b/whisperBack/whisperback.py
@@ -27,6 +27,7 @@
 
 import email.mime.text
 import os
+import re
 import threading
 
 import gi
@@ -100,8 +101,10 @@ class WhisperBack(object):
         self.smtp_host = None
         self.smtp_port = None
         self.smtp_tlscafile = None
+        self.socks_host = None
+        self.socks_port = None
 
-        # Load the python configuration file "config.py" from diffrents locations
+        # Load the python configuration file "config.py" from different locations
         # XXX: this is an absolute path, bad !
         self.__load_conf(os.path.join("/", "etc", "whisperback", "config.py"))
         self.__load_conf(os.path.join(os.path.expanduser('~'),
@@ -147,8 +150,6 @@ class WhisperBack(object):
         and raise MisconfigurationException if not.
         """
 
-        # XXX: Add sanity checks
-        
         if not self.to_address:
             raise whisperBack.exceptions.MisconfigurationException('to_address')
         if not self.to_fingerprint:
@@ -163,6 +164,46 @@ class WhisperBack(object):
             raise whisperBack.exceptions.MisconfigurationException('smtp_port')
         if not self.smtp_tlscafile:
             raise whisperBack.exceptions.MisconfigurationException('smtp_tlscafile')
+        if not self.socks_host:
+            raise whisperBack.exceptions.MisconfigurationException('socks_host')
+        if not self.socks_port:
+            raise whisperBack.exceptions.MisconfigurationException('socks_port')
+
+        # XXX: Add sanity checks
+
+        if not isinstance(self.socks_host, str):
+            raise TypeError("socks_host's type is invalid, it must " \
+                            "be a string.")
+        if len(self.socks_host) > 255:
+            raise ValueError("socks_host's value is too long: a " \
+                             "hostname must be below 255 characters.")
+        if not self.validateHostnameOrIPv4(self.socks_host):
+            raise ValueError("socks_host's value is not a correct IPv4 " \
+                             "address or hostname.")
+        try:
+            int(self.socks_port)
+        except:
+            raise TypeError("socks_port's type is invalid, it must " \
+                            "be an integer.")
+        if self.socks_port < 1 or self.socks_port > 65535:
+            raise ValueError("socks_port's value is invalid, it must " \
+                             "between 1 and 65535.")
+
+    def validateHostnameOrIPv4(self, hostname_or_ipv4):
+        """Validates a hostname or IPv4 address
+
+        pySocks is not compatible with ipv6, hostname specs follow RFC 1123
+
+        @param hostname_or_ipv4 The hostname or IPV4 to validate
+        """
+
+        # regex from http://stackoverflow.com/a/106223
+        ip_address_regex = re.compile("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");
+        hostname_regex = re.compile("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$");
+
+        if ip_address_regex.match(hostname_or_ipv4) or hostname_regex.match(hostname_or_ipv4):
+            return True
+        return False
 
     def execute_threaded(self, func, args, progress_callback=None, 
                            finished_callback=None, polling_freq=100):
@@ -178,7 +219,7 @@ class WhisperBack(object):
         @param args               the tuple to pass as arguments to `func`.
         @param progress_callback  (optional) a callback function to call
                                   every time the execution thread is polled.
-                                  It doesn't take any agument. 
+                                  It doesn't take any argument.
         @param finished_callback  (optional) a callback function to call when
                                   the execution thread terminated. It receives
                                   the exception raised by `func`, if any, or
@@ -279,7 +320,8 @@ class WhisperBack(object):
 
         self.execute_threaded(func=whisperBack.mail.send_message_tls,
                               args=(self.from_address, self.to_address,
-                                    mime_message, self.smtp_host,
+                                    mime_message, self.socks_host,
+                                    self.socks_port, self.smtp_host,
                                     self.smtp_port, self.smtp_tlscafile),
                               progress_callback=progress_callback,
                               finished_callback=finished_callback)
-- 
2.1.4

