#!/usr/bin/env python

# standard modules
import os
import re
import sys
import time                
import dbus
import logging
import fileinput

# for encryption/decryption
import base64
from Crypto.Cipher import AES

##############################################################
#    smscon - remote control utility                         #
##############################################################

VERSION  = '0.5-9'
NAME     = 'smscon'
DATE     = '18/10/2010'

##############################################################

# check if run as root
if os.geteuid() != 0:
    print 'ERROR: smscon must run as "root".'
    sys.exit(1)

##############################################################

TIME       = True

Path       = '/opt/smscon'
BootPath   = '/etc/event.d'

DaemonFile = '%s_daemon' % NAME
CodeFile   = '%s_code' % NAME
ConfigFile = '%s_config' % NAME
BootFile   = '%s_boot' % NAME
ScriptFile = '%s_script' % NAME

BootCode   = 'description "%s v%s - nokia n900 remote control utility"\n' % (NAME, VERSION) + \
"""
start on started hildon-desktop
stop on starting shutdown

console none

script
    while [ ! -e %s ]; do
        sleep 3
    done
    exec %s/%s -start > /tmp/%s_boot.log 2>&1
end script
""" % (Path, Path, NAME, NAME)

##############################################################

ConfigVars = ['SENDERNUMBER',                 
              'EMAILADDRESS',
              'COM_CHECK',
              'COM_REBOOT',
              'COM_POWEROFF',
              'COM_POWER',
              'COM_LOCATION',
              'COM_REMOTEON',
              'COM_REMOTEOFF',
              'COM_CAMERA',
              'COM_CALL',
              'COM_LOCK', 
              'COM_UNLOCK',
              'COM_TRACKON',
              'COM_TRACKOFF',
              'COM_CUSTOM',
              'USER',         
              'PASSWORD',        
              'EMAILFROM',       
              'MAILSERVER',      
              'MAILPORT',
              'REMOTEHOST',            
              'REMOTEPORT',           
              'REMOTEUSER',       
              'REMOTEPASSWORD',
              'COMMANDREPLY',
              'KEYBOARDDETECT',
              'AUTODEVICELOCK',
              'GPSTIMEOUT',
              'GPSPOLLING',
              'GPSINTERVAL',
              'GPSSEND']

EncryptedConfigVars = ['USER',         
                       'PASSWORD',        
                       'REMOTEUSER',       
                       'REMOTEPASSWORD']

##############################################################

LogPath    = '/tmp'
LogFile    = '%s.log' % NAME

if TIME:
    logging.basicConfig(filename = '%s/%s' % (LogPath, LogFile),
                        level    = logging.DEBUG,
                        format   = 'SMSCON %(levelname)s: %(message)s (%(asctime)s)',
                        datefmt  = '%d-%m-%Y %H:%M:%S')
else:
    logging.basicConfig(filename = '%s/%s' % (LogPath, LogFile),
                        level    = logging.DEBUG,
                        format   = 'SMSCON %(levelname)s: %(message)s')
    
##############################################################

def StatusSMScon():
    """
    Check if smscon_daemon is active.
    """

    Output = os.popen( 'ps ax | grep -v grep | grep %s' % DaemonFile ).read()

    if Output == '': 
        return False
    else: 
        return True

##############################################################    

def StartSMScon():
    """
    Start smscon_daemon.
    """

    os.system( '%s/%s &' % (Path, DaemonFile) )
    logging.info('%s is running' % DaemonFile)

##############################################################

def StopSMScon():
    """
    Kill smscon_daemon.
    """

    Output = os.popen('ps ax | grep -v grep | grep %s' % DaemonFile).read()

    if Output != '':
        Exp = re.compile('(\d+) root')
        PIDdaemon = Exp.search(Output).group(1)

        if PIDdaemon != '': 
            os.system('kill %s' % PIDdaemon)
            logging.info( '%s [PID=%s] is stopped' % (DaemonFile, PIDdaemon) )
            print '%s is stopped.' % DaemonFile
        else: 
            logging.error('%s status unknown' % DaemonFile)
            print 'ERROR: %s status unknown.' % DaemonFile
    else:
        logging.info('%s was not running' % DaemonFile)
        print '%s was not running.' % DaemonFile

##############################################################

def StartDaemon(Mode, Function):
    """
    Start the smscon_daemon.
    """

    os.system( '%s/%s %s "%s" &' % (Path,
                                    DaemonFile,
                                    Mode,
                                    Function) )

    print '%s started.' % DaemonFile

##############################################################

def KillSSH():
    """
    Kill ssh connection (if running).
    """

    if os.popen('pidof ssh').read().strip('\n') != '':
        os.system('kill $(pidof ssh)')
        print 'ssh connection stopped.'

##############################################################

def KillScript():
    """
    Kill smscon_script file (if running).
    """

    if os.popen('pidof %s' % ScriptFile).read().strip('\n') != '':
        os.system('kill $(pidof %s)' % ScriptFile)
        print '%s stopped.' % ScriptFile

##############################################################

def ShowLog():
    """
    Show the logfile.
    """

    if CheckFile(LogPath, LogFile) == True:
        f = open('%s/%s' % (LogPath, LogFile), 'r')
        Output = f.readlines() 
        if Output == []:
            print '%s is empty.' % LogFile 
        else:
            for Text in Output:
                print Text.strip('\n')   
        f.close()
    else:
        print "%s doesn't excist." % LogFile

##############################################################

def CheckFile(Path, File):
    """
    Check if a file exists.
    """

    try:
        f = open('%s/%s' % (Path, File), 'r')
        f.close()
    except:
        return False
    else:
        return True

##############################################################

def SetBoot():
    """
    Set start smscon_daemon at device boot.
    """
    
    if CheckFile(BootPath, BootFile) == True:
        print '%s is already active.' % BootFile  
    else:
        print "%s doesn't excist." % BootFile
        f = open('%s/%s' % (BootPath, BootFile), "w")
        f.writelines(BootCode + '\n')
        f.close()
        print 'new %s created & active.' % BootFile 

##############################################################

def ShowIMSI():
    """
    Show IMSI number from smscon_code file.
    """

    if CheckFile(Path, CodeFile) == True:
        f = open('%s/%s' % (Path, CodeFile), 'r')
        Output = f.readlines() 
        if Output == []:
            print 'ERROR: %s is empty.' % CodeFile
        else:
            for Text in Output:
                print 'IMSI = %s' % Text.strip('\n')   
        f.close()
    else:
        print "%s doesn't excist." % CodeFile

##############################################################

def Delete(Path, File):
    """
    Delete file.
    """

    if CheckFile(Path, File) == True:
        try:
            os.system( 'rm %s/%s' % (Path, File) )
        except:
            print 'ERROR: %s delete failure.' % File
        else:
            print '%s deleted.' % File
    else:
        print '%s already deleted.' % File

##############################################################

def ShowCommands():
    """
    Show available SMS commands.
    """

    SmsCommandsList  = []
    SmsCommandPreFix = 'COM_'
    TabSize          = 18

    if CheckFile(Path, ConfigFile) == True:
        f = open('%s/%s' % (Path, ConfigFile), 'r')
        ConfigLines = f.readlines() 
        if ConfigLines == []:
            print 'ERROR: %s is empty.' % ConfigFile
        else:
            try:
                for Line in ConfigLines:
                    Line = Line.strip().replace('\r', '').replace('\n', '')
                        
                    # detect sms command in text line
                    if Line.startswith(SmsCommandPreFix):
                        Data = Line.split('=')
                        Variable = Data[0].replace(' ', '')
                        Value    = Data[1].lstrip(' ')
                        if Value.startswith("'") and Value.endswith("'"):
                            SmsCommandsList.append( (Variable, Value) )
                        else:
                            print 'ERROR: syntax error in user settings (%s).' % Line

                for SmsCommand in SmsCommandsList:
                    print '%s= %s' % (SmsCommand[0].ljust(TabSize, ' '), SmsCommand[1])
            except:
                print 'ERROR: error reading user settings.'
        f.close()

    else:
        print "ERROR: %s doesn't excist." % ConfigFile

##############################################################

def InitConfig(Force=False):
    """
    Check if smscon_config exists, else create new default one.
    """

    ConfigTemplate = '# SMSCON user settings (v%s)\n' % VERSION + \
"""
# SMS number
SENDERNUMBER      = '+XXXXXXXXXXX'

# SMS commands
COM_CHECK         = 'Check'
COM_REBOOT        = 'Reboot'
COM_POWEROFF      = 'Poweroff'
COM_POWER         = 'Power'
COM_LOCATION      = 'Location'
COM_REMOTEON      = 'Remoteon'
COM_REMOTEOFF     = 'Remoteoff'
COM_CAMERA        = 'Camera'
COM_CALL          = 'Call'
COM_LOCK          = 'Lock'
COM_UNLOCK        = 'Unlock'
COM_TRACKON       = 'Trackon'
COM_TRACKOFF      = 'Trackoff'
COM_CUSTOM        = 'Script'

# Email settings
EMAILADDRESS      = 'XXXXX@XXXXXXXXXXX.XX'
# (encrypted setting)
USER              = 'CTf6qr+x76lU13erkRFHoQ=='
# (encrypted setting)
PASSWORD          = 'CTf6qr+x76lU13erkRFHoQ=='
EMAILFROM         = 'XXXXXXXX@XXXXXX.XX'
MAILSERVER        = 'XXXX.XXXXXXXX.XX'
MAILPORT          = 25

# SSH settings
REMOTEHOST        = 'XXX.XXXXXXXX.XX'
REMOTEPORT        = 22
# (encrypted setting)
REMOTEUSER        = 'CTf6qr+x76lU13erkRFHoQ=='
# (encrypted setting)
REMOTEPASSWORD    = 'CTf6qr+x76lU13erkRFHoQ=='

# Send acknowledge SMS
# (send acknowledge SMS after receiving valid SMS command: 'yes' / 'no')
COMMANDREPLY      = 'yes'

# Detect keyboard use
# (send acknowledge SMS if keyboard is slided: 'yes' / 'no')
KEYBOARDDETECT    = 'yes'

# Device lock
# (lock device after receiving valid SMS command: 'yes' / 'no')
AUTODEVICELOCK    = 'yes'

# GPS settings
# (if no GPS coordinates after 600 seconds then stop aquirering)
GPSTIMEOUT        = 600
# (number of GPS coordinates to acquire and use the most accurate in "Location"-mode)
GPSPOLLING        = 2
# (time between sending GPS coordinate SMS in "Trackon"-mode: 10 / 20 / 30 / 60 / 120)
GPSINTERVAL       = 60
# (method to send GPS coordinates: 'email' / 'sms' / 'both')
GPSSEND           = 'both'
"""

    if Force == False:
        if CheckFile(Path, ConfigFile) == True:
            return True
        else:
            print 'creating new user config file in "%s/%s".' % (Path, ConfigFile)

            f = open('%s/%s' % (Path, ConfigFile), 'w')
            f.writelines(ConfigTemplate)
            f.close()
            
            print 'WARNING: first edit %s before using smscon!' % ConfigFile

            return False
    else:
        print 'WARNING: forced to create new user config file in "%s/%s".' % (Path, ConfigFile)

        f = open('%s/%s' % (Path, ConfigFile), 'w')
        f.writelines(ConfigTemplate)
        f.close()
        
        print 'WARNING: first edit %s before using smscon!' % ConfigFile

        return False        

##############################################################

def InitScript(Force=False):
    """
    Check if smscon_script exists, else create new default one.
    """    

    ScriptTemplate = '#!/bin/sh\n' + '# %s script (v%s)\n' % (NAME, VERSION) + '# (edit below)\n\n\n'

    if Force == False:
        if CheckFile(Path, ScriptFile) == True:
            return True
        else:
            print 'creating new script file "%s/%s".' % (Path, ScriptFile)

            f = open('%s/%s' % (Path, ScriptFile), 'w')
            f.writelines(ScriptTemplate)
            f.close()

            if os.WEXITSTATUS( os.system( 'chmod 755 %s/%s' % (Path, ScriptFile) ) ) != 0:
                print 'ERROR: failed to set user permissions on %s/%s' % (Path, ScriptFile)
                sys.exit(1)            

            if os.WEXITSTATUS( os.system( 'chown root %s/%s' % (Path, ScriptFile) ) ) != 0:
                print 'ERROR: failed to set owner of %s/%s' % (Path, ScriptFile)
                sys.exit(1) 

            print 'WARNING: first edit %s (if used) before using smscon!' % ScriptFile
            return False

    else:
        print 'WARNING: forced to create new script file "%s/%s".' % (Path, ScriptFile)

        f = open('%s/%s' % (Path, ScriptFile), 'w')
        f.writelines(ScriptTemplate)
        f.close()

        if os.WEXITSTATUS( os.system( 'chmod 755 %s/%s' % (Path, ScriptFile) ) ) != 0:
            print 'ERROR: failed to set user permissions on %s/%s' % (Path, ScriptFile)
            sys.exit(1)            

        if os.WEXITSTATUS( os.system( 'chown root %s/%s' % (Path, ScriptFile) ) ) != 0:
            print 'ERROR: failed to set owner of %s/%s' % (Path, ScriptFile)
            sys.exit(1) 

        print 'WARNING: first edit %s (if used) before using smscon!' % ScriptFile
        return False        

##############################################################

def ShowFile(Path, File):
    """
    Show file.
    """

    if CheckFile(Path, File) == True:
        os.system( 'cat %s/%s' % (Path, File) )
    else:
        print 'ERROR: %s not found' % File

##############################################################
            
def ShowOptions():
    """
    Show options for smscon.
    """

    print '== smscon v%s - Nokia N900 remote control utility ==' % VERSION
    print ' Options:'
    print '   -start            : start %s' % DaemonFile
    print '   -stop             : stop %s' % DaemonFile
    print '   -status           : get %s status' % DaemonFile
    print '   -log              : show log file'
    print '   -del log          : erase log file'
    print '   -sms              : show sms commands'
    print '   -set name "value" : set user setting (name = "value")'
    print '   -config           : show %s file' % ConfigFile
    print '   -script           : show %s file' % ScriptFile
    print '   -boot             : start %s at device boot' % DaemonFile
    print '   -unboot           : remove start of %s at device boot' % DaemonFile
    print '   -help             : this help menu'
    print ' Special options (normally not needed!):'
    print '   -reset            : factory default (delete %s, %s, %s & %s file)' % ( BootFile.replace(NAME + '_', ''),
                                                                                     ConfigFile.replace(NAME + '_', ''),
                                                                                     CodeFile.replace(NAME + '_', ''),
                                                                                     ScriptFile.replace(NAME + '_', '') )
    print '   -imsi             : show imsi code file'
    print '   -del imsi         : delete %s file' % CodeFile
    print '   -init             : create default %s & %s file' % (ConfigFile.replace(NAME + '_', ''), ScriptFile.replace(NAME + '_', ''))
    print '   -del config       : delete %s file' % ConfigFile
    print '   -del script       : delete %s file' % ConfigFile

##############################################################

def SaveUserSetting(UserSetting, UserVar):
    """
    Save user setting in "smscon_config" file.
    """ 

    BlockSize = 16
    Padding   = '{'
    Pad       = lambda s: s + (BlockSize - len(s) % BlockSize) * Padding

    EncodeAES = lambda c, s: base64.b64encode(c.encrypt(Pad(s)))
    DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(Padding)

    Secret = '6983456712936753'

    File = '%s/%s' % (Path, ConfigFile)

    TabLength = 18

    if UserSetting in ConfigVars:
        try:
            for Line in fileinput.FileInput(File, inplace = True):
                if Line.startswith(UserSetting + ' ') or Line.startswith(UserSetting + '='):
                    if UserSetting in EncryptedConfigVars:
                        UserVar = "'%s'" % EncodeAES(AES.new(Secret), UserVar)
                    else:
                        if not UserVar.isdigit():
                            UserVar = "'%s'" % UserVar
                    Line = '%s' % UserSetting.ljust(TabLength) + '= %s' % UserVar
                print Line.rstrip()
        except:
            print "ERROR: couldn't save [%s = %s]" % (UserSetting, UserVar)
        else:
            if UserSetting in EncryptedConfigVars:
                print 'user setting [%s = %s] is saved.' % ( UserSetting, DecodeAES(AES.new(Secret), UserVar) )
            else:
                print 'user setting [%s = %s] is saved.' % (UserSetting, UserVar)
    else:
        print 'unknown user setting (%s)' % UserSetting

##############################################################
#    Main
##############################################################

Delay = 0.5

if sys.argv[1:] != []: # 1 or more args given
    NumOfArg = len(sys.argv[1:])

    if NumOfArg == 1: # 1 arg given
        Mode = sys.argv[1] 

        if Mode == '-init':
            if CheckFile(Path, ConfigFile) == False:
                InitConfig()
            else:
                print '%s already created.' % ConfigFile

            if CheckFile(Path, ScriptFile) == False:
                InitScript()
            else:
                print '%s already created.' % ScriptFile
          
        elif Mode == '-start':
            if CheckFile(Path, ConfigFile) == True and CheckFile(Path, ScriptFile) == True:
                if StatusSMScon() == True:
                    print '%s already active.' % DaemonFile
                else:
                    StartSMScon()
                    time.sleep(Delay)
                    if StatusSMScon() == True:
                        print '%s started.' % DaemonFile
                    else:
                        print 'ERROR: %s failed to start.' % DaemonFile
                        sys.exit(1)
            else:
                if CheckFile(Path, ConfigFile) == False:
                    print 'ERROR: no %s file present' % ConfigFile
                if CheckFile(Path, ScriptFile) == False:
                    print 'ERROR: no %s file present' % ScriptFile
                sys.exit(1)

        elif Mode == '-stop':
            StopSMScon()
            KillSSH()
            KillScript()

        elif Mode == '-reset':
            if StatusSMScon() == True:
                StopSMScon()
            else:
                print '%s already stopped.' % DaemonFile
            time.sleep(Delay)
            Delete(LogPath, LogFile)
            time.sleep(Delay)
            Delete(BootPath, BootFile)
            time.sleep(Delay)
            Delete(Path, CodeFile)
            time.sleep(Delay)
            Delete(Path, ConfigFile)
            time.sleep(Delay)            
            Delete(Path, ScriptFile)
            time.sleep(Delay) 
            KillSSH()
            KillScript()

        elif Mode == '-boot':
            SetBoot()

        elif Mode == '-unboot':
            Delete(BootPath, BootFile)                    

        elif Mode == '-status':
            if StatusSMScon() == True:
                print '%s is running.' % DaemonFile
            else:
                print '%s off.' % DaemonFile
    
        elif Mode == '-log':
            ShowLog()

        elif Mode == '-imsi':
            ShowIMSI()

        elif Mode == '-sms':
            ShowCommands()

        elif Mode == '-config':
            ShowFile(Path, ConfigFile)

        elif Mode == '-script':
            ShowFile(Path, ScriptFile)

        elif Mode == '-help':
            ShowOptions() 

        else:
            print 'unknown option (%s).' % Mode
            
    elif NumOfArg == 2: # 2 args given
        Mode = sys.argv[1]
        Func = sys.argv[2]

        if Mode == '-test' or Mode == '-test2': # /* ONLY FOR DEVELOPMENT USAGE */
            if StatusSMScon() == True:
                StopSMScon()
                print '%s stopped.' % DaemonFile
                time.sleep(Delay)
                Delete(LogPath, LogFile)
                time.sleep(Delay)
                StartDaemon(Mode, Func)
                time.sleep(Delay)
                if StatusSMScon() == False:
                    print 'ERROR: %s failed to start.' % DaemonFile
                    sys.exit(1)
                else:
                    logging.info( '"%s" TEST: %s active' % (Func.upper(), DaemonFile) )
            else:
                StartDaemon(Mode, Func)
                time.sleep(Delay)
                if StatusSMScon() == False:
                    print 'ERROR: %s failed to start.' % DaemonFile
                    sys.exit(1)
                else:
                    logging.info( '"%s" TEST: %s active' % (Func.upper(), DaemonFile) )

        elif Mode == '-del': # delete log file
            if Func == 'log':
                Delete(LogPath, LogFile)
                
            elif Func == 'imsi': # delete imsi code file
                Delete(Path, CodeFile)

            elif Func == 'config': # delete config file
                Delete(Path, ConfigFile)

            elif Func == 'script': # delete script file
                Delete(Path, ScriptFile)

            else:
                print 'unknown option (%s).' % Func

        elif Mode == '-force': # force to initialize config & script file
            if Func == 'init':
                InitConfig(True)
                InitScript(True)

            else:
                print 'unknown option (%s).' % Func            

        else:
            print 'option error.'

    elif NumOfArg == 3: # 3 args given
        Mode        = sys.argv[1]
        UserSetting = sys.argv[2]
        UserVar     = sys.argv[3]
        
        if Mode == '-set': # set user variable in config file
            SaveUserSetting(UserSetting, UserVar)
        else:
            print 'unknown option (%s).' % Mode

    else: # 4 or more args given
        print 'option error.'

else: # no arg given
    ShowOptions()  
    sys.exit(1)

