#!/usr/bin/python3
# SMS sending tool
# Copyright (c) Jan Otte, Dwarf Technologies s.r.o. All rights reserved.

import sys
import getopt
import os
import subprocess
import tempfile
from enum import IntEnum

class Loglevel(IntEnum):
    NONE = 0
    ERROR = 1
    FINISH = 2
    INFO = 3
    DEBUG = 4

class Retcode(IntEnum):
    NOERROR = 0
    PARSE = 1
    DATA = 2
    CONNECT = 3
    AUTH = 4
    EXECUTION = 5

# gets IP, mode (PWD/key), filepath (containing PWD/KEY), number and message

version = "0.1.0"
conn_timeout = 12
sms_command = "sms"
use_password = None
user = None
address = None
addr = None
msisdn = None
message = None
filename = None
loglevel = Loglevel.INFO

def print_help():
    print('\n',
'\nSMS sending script for Dwarfguard remote monitoring software, version ' + version,
'\nThis script is usually executed directly from the dwarfg daemon.',
'\nThe required parameters are:',
'\n    --address=<sending_device_address> ... "user@addr" for device to send SMS from',
'\n    --mode=[passwd|keyfile] ... tell if provided file contains password or key',
'\n    --file=<filepath> ... path to file containing password (on first line) or SSH key',
'\n    --msisdn=<number> ... GSM number to send the SMS to',
'\n    --message="SMS text" ... SMS text to be sent. Usage of basic charset is recommended',
'\n    Example:',
'\n        --address=root@1.2.3.4 --mode=keyfile --file=./my_key --msisdn="00420123456789" --message="My sms text"',
'\n    Script returns 0 in case of SMS being sent successfully.',
'\n    If error is detected, non-zero is returned and error description spit on STDERR.\n'
)

def pg_msg(level, output=sys.stdout, *args):
    if level <= loglevel:
        if level == Loglevel.FINISH:
            print("", *args, file=output, sep='')
        else:
            print(*args, file=output, sep='')

def mprint(level, *args):
    pg_msg(level, sys.stdout, *args)

def mprint_err(*args):
    pg_msg(Loglevel.ERROR, sys.stderr, "Error: ", *args)

def mprint_finerr(retval, *args):
    pg_msg(Loglevel.FINISH, sys.stderr, "Error: ", *args)
    exit(retval)

def mprint_finok(retval, *args):
    pg_msg(Loglevel.FINISH, sys.stdout, "OK: ", *args)
    exit(retval)

def main():
    acmd = []
    ccmd = []
    for t in [ x for x in (('use_password', use_password), ('address', address), ('msisdn', msisdn), ('message', message), ('filename', filename)) if x[1] is None ]:
        mprint_finerr(Retcode.PARSE, "Parameter '", t[0], "' was not set (try -h for help).")
    for t in [ x for x in (('user', user), ('addr', addr)) if x[1] is None or 0 == len(x[1]) ]:
        mprint_finerr(Retcode.PARSE, "Address is incorrect (should be user@addr but is '", address, "')")
    try:
        with open(filename) as f:
            lines = [ line.strip() for line in f ]
    except OSError:
        mprint_finerr(Retcode.DATA, "Unable to read password/SSH key (\"", filename, "\")")
    if len(lines) < 1:
        mprint_finerr(Retcode.DATA, "Password or SSH key (\"", filename, "\") is empty.")
    realcmd = [ sms_command, msisdn, '"'+message+'"' ]
    if use_password:
        os.environ["SSHPASS"] = lines[0]
        basecmd = [ 'sshpass', '-e', 'ssh', '-o', 'StrictHostKeyChecking=no', address ]
    else:
        basecmd = [ 'ssh', '-o', 'StrictHostKeyChecking=no', '-o', 'BatchMode=yes', '-i', filename, address ]
    acmd = [ 'nc', '-w', str(conn_timeout), '-z', addr, '22' ]
    mprint(Loglevel.DEBUG, '\tRunning: ', ' '.join(str(i) for i in acmd))
    res = subprocess.run(acmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, text=True)
    if 0 != res.returncode:
        mprint_finerr(Retcode.CONNECT, "Unable to connect to port 22 of '", addr, "'")
    acmd = basecmd + [ 'exit' ]
    mprint(Loglevel.DEBUG, '\tRunning: ', ' '.join(str(i) for i in acmd))
    res = subprocess.run(acmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
    if 0 != res.returncode:
        mprint_finerr(Retcode.AUTH, "SSH authentication error when connecting to '", addr, "'")
    acmd = basecmd + realcmd
    mprint(Loglevel.DEBUG, '\tRunning: ', ' '.join(str(i) for i in acmd))
    res = subprocess.run(acmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
    if 0 != res.returncode:
        mprint_finerr(Retcode.EXECUTION, "Failed to send SMS via '", address, "'")
    mprint_finok(Retcode.NOERROR, "Message send successfully")

if __name__ == "__main__":
    try:
        opts, args = getopt.gnu_getopt(sys.argv[1:], 'h', [ 'help', 'mode=', 'address=', 'msisdn=', 'message=', 'file=' ])
        for opt, arg in opts:
            if opt == "-h" or opt == "--help":
                print_help()
                mprint_finerr(Retcode.PARSE, "Help requested and printed, no SMS was sent.")
            if opt == "--mode":
                if 'passwd' == str(arg):
                    use_password = True
                elif 'keyfile' == str(arg):
                    use_password = False
                else:
                    mprint_finerr(Retcode.PARSE, "Invalid method provided (use 'passwd' or 'keyfile')")
            if opt == "--address":
                if 2 != len(arg.split('@')):
                    mprint_finerr(Retcode.PARSE, "Incorrect address format (user@addr): ", arg)
                address = str(arg)
                (user, addr) = address.split('@')
            if opt == "--file":
                filename=str(arg)
            if opt == "--msisdn":
                msisdn=str(arg)
            if opt == "--message":
                message=str(arg)
    except getopt.GetoptError:
        mprint_finerr(Retcode.PARSE, "Invalid (not-recognized) parameter among: ", ' '.join(sys.argv[1:]))
    main()
