Repository: privacyidea/privacyidea

Issue:
SMPPLib requires message to be in binary format
The [python-smpplib](https://github.com/python-smpplib/python-smpplib) package requires the message to be a byte string (if not otherwise noted). This can be done by simply converting the message to binary format here:
https://github.com/privacyidea/privacyidea/blob/e45f2be85f38ea2838fa43b3ef59d65d7d00530a/privacyidea/lib/smsprovider/SmppSMSProvider.py#L98
On the other hand we could implement a check for the required character set with `smpplib.gsm.make_parts()` as described in the here:
https://github.com/python-smpplib/python-smpplib#python-smpplib


Discussion:

Resolving patch:
diff --git a/privacyidea/lib/smsprovider/SmppSMSProvider.py b/privacyidea/lib/smsprovider/SmppSMSProvider.py
--- a/privacyidea/lib/smsprovider/SmppSMSProvider.py
+++ b/privacyidea/lib/smsprovider/SmppSMSProvider.py
@@ -21,7 +21,7 @@
 #               GNU Affero General Public License
 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-__doc__="""This is the SMSClass to send SMS via SMPP protocol to SMS center
+__doc__ = """This is the SMSClass to send SMS via SMPP protocol to SMS center
 It requires smpplib installation, this lib works with ascii only, but message support unicode 
 
 """
@@ -31,14 +31,10 @@
 from privacyidea.lib.utils import parse_int
 import logging
 import traceback
-log = logging.getLogger(__name__)
+import smpplib
+import smpplib.gsm
 
-try:
-    import smpplib
-    import_successful = True
-except ImportError:     # pragma: no cover
-    log.warning("Failed to import smpplib.")
-    import_successful = False
+log = logging.getLogger(__name__)
 
 
 class SmppSMSProvider(ISMSProvider):
@@ -51,10 +47,6 @@ def submit_message(self, phone, message):
         :param message: the message to submit to the phone
         :return:
         """
-        if not import_successful:  # pragma: no cover
-            log.error("smpplib can not be found!")
-            raise SMSError(404, "smpplib can not be found!")
-
         log.debug("submitting message {0!s} to {1!s}".format(message, phone))
         if not self.smsgateway:
             # this should not happen. We now always use sms gateway definitions.
@@ -83,20 +75,24 @@ def submit_message(self, phone, message):
         client = None
         error_message = None 
         try:
+            msg_parts, encoding_flag, msg_type_flag = smpplib.gsm.make_parts(message)
             client = smpplib.client.Client(smsc_host,
                                            smsc_port)
             client.connect()
             r = client.bind_transmitter(system_id=sys_id,
                                         password=passwd)
-            log.debug("bind_transmitter returns {0!r}".format(r))
-            r = client.send_message(source_addr_ton=s_addr_ton,
-                                    source_addr_npi=s_addr_npi,
-                                    source_addr=s_addr,
-                                    dest_addr_ton=d_addr_ton,
-                                    dest_addr_npi=d_addr_npi,
-                                    destination_addr=phone,
-                                    short_message=message)
-            log.debug("send_message returns {0!r}".format(r))
+            log.debug("bind_transmitter returns {0!r}".format(r.get_status_desc()))
+            for part in msg_parts:
+                r = client.send_message(source_addr_ton=s_addr_ton,
+                                        source_addr_npi=s_addr_npi,
+                                        source_addr=s_addr,
+                                        dest_addr_ton=d_addr_ton,
+                                        dest_addr_npi=d_addr_npi,
+                                        destination_addr=phone,
+                                        short_message=part,
+                                        data_coding=encoding_flag,
+                                        esm_class=msg_type_flag)
+                log.debug("send_message returns {0!r}".format(r.get_status_desc()))
 
         except Exception as err:
             error_message = "{0!r}".format(err)
diff --git a/privacyidea/lib/tokens/emailtoken.py b/privacyidea/lib/tokens/emailtoken.py
--- a/privacyidea/lib/tokens/emailtoken.py
+++ b/privacyidea/lib/tokens/emailtoken.py
@@ -312,7 +312,7 @@ def create_challenge(self, transactionid=None, options=None):
             except Exception as e:
                 info = _("The PIN was correct, but the "
                          "EMail could not be sent!")
-                log.warning(info + " ({0!s})".format(e))
+                log.warning(info + " ({0!r})".format(e))
                 log.debug(u"{0!s}".format(traceback.format_exc()))
                 return_message = info
                 if is_true(options.get("exception")):
diff --git a/privacyidea/lib/tokens/smstoken.py b/privacyidea/lib/tokens/smstoken.py
--- a/privacyidea/lib/tokens/smstoken.py
+++ b/privacyidea/lib/tokens/smstoken.py
@@ -353,7 +353,7 @@ def create_challenge(self, transactionid=None, options=None):
             except Exception as e:
                 info = _("The PIN was correct, but the "
                          "SMS could not be sent!")
-                log.warning(info + " ({0!s})".format(e))
+                log.warning(info + " ({0!r})".format(e))
                 log.debug("{0!s}".format(traceback.format_exc()))
                 return_message = info
                 if is_true(options.get("exception")):


Test patch:
diff --git a/tests/smppmock.py b/tests/smppmock.py
--- a/tests/smppmock.py
+++ b/tests/smppmock.py
@@ -25,6 +25,7 @@
 )
 
 from collections import namedtuple
+from mock import Mock
 
 try:
     from collections import Sequence, Sized
@@ -114,13 +115,17 @@ def _on_init(self, SMPP, host, port):
     def _on_transmitter(self, SMPP, systemid, password):
         if systemid != self.systemid or password != self.password:
             raise Exception("Wrong credentials")
-        return None
+        BindTransmitterRespMock = Mock()
+        BindTransmitterRespMock.get_status_desc.return_value = 'No Error'
+        return BindTransmitterRespMock
 
     def _on_send_message(self, SMPP, source_addr_ton=None, source_addr_npi=None,
                          source_addr=None, dest_addr_ton=None,
                          dest_addr_npi=None, destination_addr=None,
-                         short_message=None):
-        pass
+                         short_message=None, data_coding=None, esm_class=None):
+        SubmitSMMock = Mock()
+        SubmitSMMock.get_status_desc.return_value = 'No Error'
+        return SubmitSMMock
 
     def start(self):
         import mock
diff --git a/tests/test_lib_smsprovider.py b/tests/test_lib_smsprovider.py
--- a/tests/test_lib_smsprovider.py
+++ b/tests/test_lib_smsprovider.py
@@ -633,7 +633,6 @@ class SmppSMSTestCase(MyTestCase):
               'SMSC_PORT': "1234",
               'SYSTEM_ID': "privacyIDEA",
               'PASSWORD': "secret",
-              'SYSTEM_ID': "privacyIDEA",
               'S_ADDR_TON': "0x5",
               'S_ADDR_NPI': "0x1",
               'S_ADDR': "privacyIDEA",