#!/usr/bin/env python

##############################################################
#    smscon_daemon - remote control daemon                   #
##############################################################

VERSION = '0.4.4'

import os
import sys
import time
import string
import dbus
import logging
import location
import gobject
import re
import pexpect
import smtplib
import fileinput
import urllib
import random
from operator import itemgetter 
from subprocess import *
from dbus.mainloop.glib import DBusGMainLoop
from email.MIMEText import MIMEText
from email.MIMEImage import MIMEImage
from email.MIMEMultipart import MIMEMultipart

############################################################################################################################
#    variables
############################################################################################################################

TIME                         = False                             # if set, show time in smscon_log file

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

Path                         = '/usr/bin/'                       # main path of files
BootPath                     = '/etc/event.d/'                   # upstart directory

CodeFile                     = 'smscon_code'                     # code file name
BootFile                     = 'smscon_boot'                     # name of upstart file
ConfigFile                   = 'smscon_config'                   # name of config file
DaemonFile                   = 'smscon_daemon'                   # name of daemon file

LocalHost                    = 'localhost'                       # nokia N900 local address for reverse-ssh 
LocalPort                    = 8080                              # nokia N900 local port for reverse-ssh

TimeOut                      = 5                                 # timeout in seconds
PingMax                      = 3                                 # number of ping commands for connection status check

PhotoName                    = 'frontcam.jpg'                    # frontcam filename
MapName                      = 'gpsmap.png'                      # googlemaps filename

Gps                          = 0
GpsList                      = []
GpsActive                    = False
StartTime                    = 0
EndTime                      = 0

SenderNumber                 = ''

EnableKeyboardDetect         = False

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

LogPath                      = '/tmp/'                           # path of log file
LogFile                      = 'smscon.log'                      # log file name

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

##############################################################
#    time
##############################################################

def GetTime():
    """
    Convert time to readable format.
    """

    t = time.localtime( time.time() )

    if t[2] <= 9:
        t2 = '0' + str(t[2])
    else:
        t2 = t[2]

    if t[1] <= 9:
        t1 = '0' + str(t[1])
    else:
        t1 = t[1]

    if t[0] <= 9:
        t0 = '0' + str(t[0])
    else:
        t0 = t[0]

    if t[3] <= 9:
        t3 = '0' + str(t[3])
    else:
        t3 = t[3]

    if t[4] <= 9:
        t4 = '0' + str(t[4])
    else:
        t4 = t[4]

    if t[5] <= 9:
        t5 = '0' + str(t[5])
    else:
        t5 = t[5]
    
    return "%s:%s:%s %s/%s/%s" % (t3, t4, t5, t2, t1, t0)

##############################################################
#    sms
##############################################################

def OctifyMessage(str):
    """    
    Returns a list of octet bytes representing each char of the input str.               
    """                                       

    bytes    = map(ord, str)
    BitsCons = 0     
    RefBit   = 7     
    Octets   = []          

    while len(bytes):
        Byte = bytes.pop(0)
        Byte = Byte >> BitsCons
                                   
        try:                       
            NextByte = bytes[0]
            BitsToCopy = (NextByte & (0xff >> RefBit)) << RefBit
            Octet = (Byte | BitsToCopy)                                     
        except:
            Octet = (Byte | 0x00)

        if BitsCons != 7:
            Octets.append(Byte | BitsToCopy)
            BitsCons += 1               
            RefBit   -= 1               
        else:                                   
            BitsCons = 0                
            RefBit   = 7                

    return Octets

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

def CreatePDUmessage(Number, Message):
    """                       
    Return list of bytes to represent a valid PDU message.
    """                                                     

    # 06 is Dutch mobile number prefix / +31 is Dutch phone country code.
    #    "06XXXXXXXX" = 10 chars
    #   "316XXXXXXXX" = 11 chars
    #  "+316XXXXXXXX" = 12 chars
    # "00316XXXXXXXX" = 13 chars

    # problem to solve: only works without country code!
    Number = Number.replace('+31', '0') 

    # if number has odd length, add trailing 'F'
    if (len(Number) % 2) == 1:                                
        Number += 'F'                           

    NumberLength = len(Number)

    OctifiedNumber  = [ SemiOctify(Number[i:i+2]) for i in range(0, NumberLength, 2) ]
    OctifiedMessage = OctifyMessage(Message)                                                      

    Header        = 1                  # Header 1
    FirstOctet    = 10                 # First octet of this SMS-DELIVER message (10 dec) (A hex)
                                       # TP-MR (not used)
    TypeOfAddress = 129                # Type of addres of sender number (129 dec) (81 hex)          
    MessageLength = len(Message)       # Length of message                                                        

    PDUmessage = [Header,
                  FirstOctet,
                  NumberLength,
                  TypeOfAddress]       # Header
                                       # First octet of the SMS-SUBMIT message
                                       # Length of number
                                       # Type of address 
    
    PDUmessage.extend(OctifiedNumber)  # Number in semi-octets                                           
    PDUmessage.append(0)               # TP-PID                                  
    PDUmessage.append(0)               # TP-DCS
                                       # TP-VP (not used)
    PDUmessage.append(MessageLength)   # TP-User-Data-Length (length of message)                                              
    PDUmessage.extend(OctifiedMessage) # TP-User-Data (message)

    return PDUmessage                                                             

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

def SemiOctify(str):
    """          
    Expects a string containing two digits, then returns an octet;                     
    first nibble in the octect is the first digit and
    the second nibble represents the second digit.                      
    """                                    

    try:                                   
        D1 = int(str[0])          
        D2 = int(str[1])          
        Octet = (D2 << 4) | D1
    except:                                 
        Octet = (1 << 4) | D1      

    return Octet

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

def DeOctifyMessage(Array):
    """
    Deoctify received message and return text string.
    """
    
    RefBit   = 1
    Doctect  = []    
    ByteNext = 0x00    

    for i in Array:
        ByteCurrent = ((i & (0xff >> RefBit)) << RefBit) >> 1
        ByteCurrent |= ByteNext                                      

        if RefBit != 7:
            Doctect.append(ByteCurrent)
            ByteNext = (i & (0xff << (8 - RefBit)) ) >> 8 - RefBit
            RefBit += 1
            
        else:                                                                  
            Doctect.append(ByteCurrent)                                        
            ByteNext = (i & (0xff << (8 - RefBit)) ) >> 8 - RefBit
            Doctect.append(ByteNext)                                        
            ByteNext = 0x00                                                   
            RefBit = 1                                               

    return ''.join([chr(i) for i in Doctect])

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

def SMSreceive(PDUmessage, MessageCenter, SomeString, Number):
    """
    Receive SMS command and execute command.
    """

    global SenderNumber
    SenderNumber = Number 

    MessageLength = int(PDUmessage[18])
    MessageArray  = PDUmessage[19:len(PDUmessage)]

    Message = DeOctifyMessage(MessageArray) # decode sms message as plain text

    ExecuteCommand(Message, SenderNumber) # execute sms command

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

def SMSsend(Number, Message):
    """
    Send reply SMS to sender number.
    """

##    Dbus way of sms sending:
##
##    bus = dbus.SystemBus()
##    smsobject = bus.get_object('com.nokia.phone.SMS',
##                               '/com/nokia/phone/SMS/ba212ae1')
##    smsiface = dbus.Interface(smsobject,
##                              'com.nokia.csd.SMS.Outgoing')
##
##    arr = dbus.Array( CreatePDUmessage(Number, Message) )
##    msg = dbus.Array([arr])
##
##    smsiface.Send(msg, '')


    # this below works but has no internal checks!
    gsm = pexpect.spawn('pnatd') # open 'pnatd'
    gsm.send('AT\r') # test is gsm modem is available
    time.sleep(0.25)
    gsm.send('AT+CMGF=1\r') # set gsm modem to sms text
    time.sleep(0.25)
    gsm.send( 'AT+CMGS="%s"\r' % Number ) # set sms number
    gsm.send( '%s' % Message + chr(26) ) # set sms message & send
    gsm.sendeof()

    time.sleep(3)

##############################################################
#    email
##############################################################

def EMAILsend(EmailTo, Subject, Text, Attachment=None):
    """
    Send email to owner of the device.
    """

    RandomNumber = random.randint(1, 100000)
    
    HtmlTextPage = \
""" 
<html> 
<head></head> 
<body> 
<p>
%s<br>
</p>
</body> 
</html>
""" % Text

    HtmlImagePage = \
""" 
<html> 
<head></head> 
<body> 
<p>
%s<br>
""" % Text + \
'<img src="cid:map%s" alt="map of location of Nokia N900" /><br>' % RandomNumber + \
"""
</p>
</body> 
</html>
"""

    Message            = MIMEMultipart('related') 
    Message['Subject'] = Subject
    Message['From']    = 'NokiaN900'
    Message['To']      = EmailTo

    # html text only
    if Attachment == None: 
        Message.attach( MIMEText(HtmlTextPage, 'html') )

    # html text & image provided  
    else: 
        Message.attach( MIMEText(HtmlImagePage, 'html') )

        try:
            File = open(Attachment, 'rb')
            Image = MIMEImage( File.read() )
            File.close()
        except:
            logging.error('failed to send email message to "%s" (attachment not found)' % EmailTo)
            return
        else:
            Image.add_header('Content-ID', '<map%s>' % RandomNumber)
            Message.attach(Image)

    try:
        server = smtplib.SMTP(MAILSERVER, MAILPORT)
        #server.set_debuglevel(True)
        server.login(USER, PASSWORD)
        server.sendmail( EMAILFROM,
                         EmailTo,
                         Message.as_string() )
        server.quit()
    except smtplib.SMTPException, e:
        logging.error( 'failed to send email message to "%s" (%s)' % (EmailTo, e) )
    else:
        logging.info('send email message to "%s"' % EmailTo)

##############################################################
#   save new number
##############################################################

def SaveNumber(SenderNumber):
    """
    Save new SenderNumber as SENDERNUMBER in "smscon_config" file.
    """ 

    global SENDERNUMBER

    # update SENDERNUMBER
    SENDERNUMBER = SenderNumber

    UserVar = 'SENDERNUMBER'
    File    = '%s' % (Path + ConfigFile)

    try:
        for Line in fileinput.FileInput(File, inplace = True):
            if Line.startswith(UserVar):
                Line = "%s      = '%s'" % (UserVar, SenderNumber)
            print Line.rstrip()
    except:
        logging.error("couldn't save new SMS number (%s)" % SenderNumber)
    else:
        logging.info( 'new SMS number (%s) saved in %s' % (SenderNumber, ConfigFile) )

##############################################################
#    command execution
##############################################################

def ExecuteCommand(Command, SenderNumber):
    """
    Send an acknowledge reply (if set) and execute the command.
    """

    Delay = 2

    CommandList = [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]
    
    if Command in CommandList:

        logging.info( 'received SMS command [%s] from "%s"' % (Command, SenderNumber) )

        # if new SenderNumber is different from stored SENDERNUMBER in "smscon_config" file, store new SenderNumber in "smscon_config" file.
        if SenderNumber != SENDERNUMBER:
            SaveNumber(SenderNumber)

        # set "Internet connection" behavior
        EnableConnectAutomatically('GPRS')
        time.sleep(Delay)

##        print 'AUTODEVICELOCK=', AUTODEVICELOCK
        
        # auto device locking
        if AUTODEVICELOCK == 'yes' and Command != COM_UNLOCK:
            EnableDeviceLock(False)
        elif AUTODEVICELOCK == 'no' and Command != COM_LOCK:
            DisableDeviceLock(False)

##        print 'COMMANDREPLY=', COMMANDREPLY

        # command reply
        if COMMANDREPLY == 'yes':
            Text = 'NOKIA N900\n' + \
                   '%s\n' % GetTime() + \
                   '-reply-\n' + \
                   'command (%s) accepted.\n' % Command
            if AUTODEVICELOCK == 'yes' and Command != COM_UNLOCK:
                SMSsend(SenderNumber,
                        Text + \
                        '-security-\n' + \
                        'phone is locked.\n')
            else:
                SMSsend(SenderNumber, Text)

##        print 'KEYBOARDDETECT=', KEYBOARDDETECT

        # keyboard detection
        global EnableKeyboardDetect
        if KEYBOARDDETECT == 'yes':
            EnableKeyboardDetect = True
        elif KEYBOARDDETECT == 'no':
            EnableKeyboardDetect = False      

##        print 'Command=', Command

        # command execution
        if Command == COM_CHECK:
            SMSsend(SenderNumber,
                    'NOKIA N900\n' + \
                    '%s\n' % GetTime() + \
                    '-check-\n' + \
                    'smscon is active.\n')        

        elif Command == COM_REBOOT or Command == COM_POWEROFF:
            EnableSystem(Command)

        elif Command == COM_POWER:
            EnablePowerStatus()

        elif Command == COM_LOCATION:
            EnableConnection('GPRS')
            EnableGPStracking('single')

        elif Command == COM_REMOTEON:
            EnableConnection('GPRS')
            EnableSSHcontrol()

        elif Command == COM_REMOTEOFF:
            DisableSSHcontrol()

        elif Command == COM_CAMERA:
            EnableConnection('GPRS')
            EnableCamera()

        elif Command == COM_CALL:
            EnablePhonecall()

        elif Command == COM_LOCK:
            EnableDeviceLock(True)

        elif Command == COM_UNLOCK:
            DisableDeviceLock(True)

        elif Command == COM_TRACKON:
            EnableConnection('GPRS')
            EnableGPStracking('multi')

        elif Command == COM_TRACKOFF:
            DisableGPStracking()
    else:
        logging.info('no SMS command found')

##############################################################
#   device lock
##############################################################

def EnableDeviceLock(SendReply=False):
    """
    Lock device after receiving valid sms command.
    """

    os.system ('dbus-send --system --type=method_call \
               --dest=com.nokia.system_ui /com/nokia/system_ui/request \
               com.nokia.system_ui.request.devlock_open \
               string:"com.nokia.mce" \
               string:"/com/nokia/mce/request" string:"com.nokia.mce.request" \
               string:"devlock_callback" uint32:"3"')

    logging.info('Nokia device is locked')

    if SendReply:
        SMSsend(SenderNumber,
                'NOKIA N900\n' + \
                '%s\n' % GetTime() + \
                '-lock-\n' + \
                'device has been locked.')

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

def DisableDeviceLock(SendReply=False):
    """
    Unlock device.
    """

    os.system ('dbus-send --system --type=method_call \
               --dest=com.nokia.system_ui /com/nokia/system_ui/request \
               com.nokia.system_ui.request.devlock_close \
               string:"com.nokia.mce" \
               string:"/com/nokia/mce/request" string:"com.nokia.mce.request" \
               string:"devlock_callback" uint32:"0"')

    logging.info('Nokia device is unlocked')

    if SendReply:
        SMSsend(SenderNumber,
                'NOKIA N900\n' + \
                '%s\n' % GetTime() + \
                '-lock-\n' + \
                'device is unlocked.')

##############################################################
#   system
##############################################################

def EnableSystem(Com):
    """
    Run system command.
    """

    logging.info('executing command "%s"' % Com)
    
    time.sleep(10)
    os.system(Com)
    
    gobject.MainLoop().quit()

##############################################################
#   camera
##############################################################

def EnableCamera():
    """
    Get picture of front camera and send it to email address.
    """

    if TakeCamPicture() == True:
        try:
            EMAILsend(EMAILADDRESS,
                      '%s command / Nokia N900' % COM_CAMERA,
                      'This is a frontcam picture from the Nokia N900 device:',
                      LogPath + PhotoName)
        except:
            logging.info('camera picture failed to send to "%s"' % EMAILADDRESS)
        else:
            logging.info('camera picture send to "%s"' % EMAILADDRESS)

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

def TakeCamPicture():
    """
    Acquire front camera picture.
    """

    Delay = 5

    CamName     = 'video1'
    CamRes      = (640,480)
    CamEncoding = 'jpegenc'

    try:
        os.popen('gst-launch v4l2camsrc device=/dev/%s num-buffers=1 ! video/x-raw-yuv,width=%s,height=%s ! ffmpegcolorspace ! %s ! filesink location=%s' % (CamName,
                                                                                                                                                             CamRes[0],
                                                                                                                                                             CamRes[1],
                                                                                                                                                             CamEncoding,
                                                                                                                                                             LogPath + PhotoName) )
    except:
        logging.error('camera picture failed (capture error)')
        return False
    else:
        time.sleep(Delay)

        if os.path.isfile(LogPath + PhotoName) == True:
            logging.info('camera picture acquired')
            return True
        else:
            logging.error('camera picture failed (file not found)')
            return False

##############################################################
#    phone call
##############################################################

def EnablePhonecall():
    """
    Make phone call to sender number.
    """

    try:
        os.system('run-standalone.sh dbus-send --system --type=method_call --dest=com.nokia.csd.Call /com/nokia/csd/call com.nokia.csd.Call.CreateWith string:"%s" uint32:0' % SENDERNUMBER )
        logging.info('making phonecall to "%s"' % SenderNumber)          
    except:
        logging.error('failed to make phonecall to "%s"' % SenderNumber)

##############################################################
#    data connection
##############################################################

def StatusConnection(Log = False): 
    """
    Get info of current data connection; type and status
    """

    # (this is very simplistic done)
    try:
        Wlan = os.popen('ifconfig | grep "wlan0"').read()
    except:
        Wlan = ''    

    try:
        Gprs = os.popen('ifconfig | grep "gprs0"').read() 
    except:
        Wlan = ''

    # wlan data connection active
    if Wlan != '' and Gprs == '':
        Type = 'WLAN'
        if Log == True:
            logging.info('network detected (%s)' % Type)

        return Type

    # gprs data connection active
    elif Wlan == '' and Gprs != '':
        Type = 'GPRS'
        if Log == True:
            logging.info('network detected (%s)' % Type)

        return Type
    
    # no data connection active
    else:
        return 'NONE'    

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

def EnableConnection(NewType):
    """
    Connect to specific data connection ('WLAN' / 'GPRS').
    ('WLAN' not officialy supported)
    """

    Delay = 10

    CurrentType = StatusConnection(False)

    # not connected to a data connection
    if CurrentType == 'NONE':
        pass

    # already connected to a data connection
    elif (CurrentType == 'WLAN' and NewType == 'GPRS') or (CurrentType == 'GPRS' and NewType == 'WLAN'):

        # disconnect current data connection before connecting to new type data connection
        DisableConnection(CurrentType) 
        
    # already connected to same type data connection
    elif (CurrentType == 'WLAN' and NewType == 'WLAN') or (CurrentType == 'GPRS' and NewType == 'GPRS'):
        
        logging.info('network (%s) already connected' % CurrentType)
        return True
     
    if NewType == 'GPRS':
        IAP = GetCurrentGPRSname()
    elif NewType == 'WLAN':
        IAP = GetCurrentLANname()

##    logging.debug( '[%s]' % IAP)

    if IAP == 'NONE':
        logging.error('no available data network found')
        return False

    else:
        # initiate new data connection
        os.system( 'dbus-send --system --type=method_call --dest=com.nokia.icd /com/nokia/icd com.nokia.icd.connect string:"%s" uint32:0' % IAP )
        
        time.sleep(Delay)

        # check new connection is working
        NewType = StatusConnection(False)
        
        if NewType == 'WLAN' or NewType == 'GPRS':
            logging.info('new network (%s) connection succesfull' % NewType)
            return True

        elif NewType == 'NONE':
            logging.error('failed to connect to "%s" (could not initiate)' % NewType)
            return False

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

def DisableConnection(CurrentType): 
    """
    Disconnect any current WIFI/GPRS dataconnection.
    """

    Delay = 5

    os.system('dbus-send --system --dest=com.nokia.icd2 /com/nokia/icd2 com.nokia.icd2.disconnect_req uint32:0x8000')
    logging.info('current network (%s) disconnected' % CurrentType)

    time.sleep(Delay)

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

def GetCurrentGPRSname(): 
    """
    Get name of available GPRS data connection from current inserted SIM card in Nokia device.
    """

    NetworkList = GetAvailableNetworks()

    for Network in NetworkList:
        if Network[0] == 'GPRS': # (Type, IAP, Name, Imsi)
            if Network[3] == GetCode('IMSI'):
##                logging.debug('[%s]' % Network[1])
                return Network[1].replace('@32@', ' ')

    return 'NONE'

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

def GetCurrentLANname(): # UNSUPPORTED FEATURE!
    """
    Get name of available LAN data connections.
    """

    NetworkList = GetAvailableNetworks()

    for Network in NetworkList:
        if Network[0] == 'LAN': # (Type, IAP, Name)
##            logging.debug('[%s]' % Network[1])
            return Network[1]

    return 'NONE'
    
###############################

def GetAvailableNetworks():
    """
    Get list of all available data connections (stored under "Internet connections" in menu).
    """

    Output = os.popen('gconftool -R /system/osso/connectivity/IAP | grep IAP').read()
    E = re.compile('/system/osso/connectivity/IAP/(\S+):')
    IAPlist = E.findall(Output) 

    NetworkList = []

    for IAP in IAPlist:
        Type = os.popen('gconftool -g /system/osso/connectivity/IAP/%s/type' % IAP).read().replace('_INFRA', '').strip('\n')
        if Type == 'GPRS':
            Name = os.popen('gconftool -g /system/osso/connectivity/IAP/%s/name' % IAP).read().strip('\n')
            Imsi = os.popen('gconftool -g /system/osso/connectivity/IAP/%s/sim_imsi' % IAP).read().strip('\n')
            NetworkList.append( (Type, IAP, Name, Imsi) )
        elif Type == 'WLAN':
            Name = os.popen('gconftool -g /system/osso/connectivity/IAP/%s/name' % IAP).read().strip('\n')
            NetworkList.append( (Type, IAP, Name) )

##    logging.debug('[%s]' % NetworkList)

    return NetworkList # = [ (Type, IAP, Name, Imsi), (Type, IAP, Name), etc.]

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

def EnableConnectAutomatically(Mode):
    """
    Set Nokia device data connection behavior.
    """    

    Delay = 2

    if StatusConnectAutomatically() != Mode:
        SetConnectAutomatically(Mode)
        time.sleep(Delay)

    Reply = StatusConnectAutomatically()
    if Reply == Mode:
        logging.info('internet connection behavior set to "%s"' % Mode)
        return True
    else:
        logging.error('failed to set internet connection behavior (%s)' % Reply)
        return False

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

def SetConnectAutomatically(Mode):
    """
    Set Nokia device data connection behavior.
    """

    if   Mode == 'ASK':
        String = ''
    elif Mode == 'WLAN':
        String = 'WLAN_INFRA'
    elif Mode == 'GPRS':        
        String = 'GPRS'
    elif Mode == 'ANY':
        String = '*'
        
    # set setting "Connect automatically" / Options are: [WLAN_INFRA], [GPRS], [*] <=='any', [] <=='ask'
    os.system('gconftool-2 -s --type list --list-type string /system/osso/connectivity/network_type/auto_connect [%s]' % String) 

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

def StatusConnectAutomatically():
    """
    Get Nokia device data connection behavior.
    """ 

    Mode = os.popen('gconftool-2 -g --list-type string /system/osso/connectivity/network_type/auto_connect').read().strip('\n') 

    if Mode == '[]':
        return 'ASK'
    elif Mode == '[*]':
        return 'ANY'
    elif Mode == '[GPRS]':
        return 'GPRS'    
    elif Mode == '[WLAN_INFRA]':
        return 'WLAN'
    else:
        return 'UNKNOWN'

##############################################################
#    power status
##############################################################

def EnablePowerStatus():
    """
    Get battery charge and send sms reply.
    """

    ChargePerc = dev_obj.GetProperty('battery.charge_level.percentage') # charge amount of battery in percentage

    logging.info('batterycharge = %s %%' % ChargePerc)

    SMSsend(SenderNumber,
            'NOKIA N900\n' + \
            '%s\n' % GetTime() + \
            '-battery-\n' + \
            'charge = %s%%' % ChargePerc )

##############################################################
#    ssh 
##############################################################

def EnableSSHcontrol():
    """
    Start the ssh routine.
    """

    if StatusSSH() == False:
        if StatusRemoteHost() == False:
            logging.error('ssh connection failure to "%s"' % REMOTEHOST)
            return
        else:
            StartSSH()
            return
    else:
        logging.info('ssh connection is already active')
        return

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

def DisableSSHcontrol():
    """
    Stop the ssh routine.
    """

    if StatusSSH() == True:
        logging.info('trying to disable ssh connection to "%s"' % REMOTEHOST)
        StopSSH()
    else:
        logging.info('ssh connection is already disabled')

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

def StatusRemoteHost():
    """
    Check if remote host is available with a ping command and return
    - false; no or bad ping 
    - true;  good ping (remotehost is available)
    """

    if os.WEXITSTATUS( os.system( 'ping -c %s %s >> /dev/null 2>&1' % (PingMax, REMOTEHOST) ) ) == 0:
        return True
    else:
        return False 
        
###############################

def StartSSH():
    """
    Start reverse-ssh connection to remote host.
    """

    logging.info('connecting to "%s"' % REMOTEHOST)


    (Output, ExitStatus) = pexpect.run( 'ssh -n -N -T -f -p %s -R %s:%s:%s %s@%s &' % (REMOTEPORT, LocalPort, LocalHost, REMOTEPORT, REMOTEUSER, REMOTEHOST),
                                        events = { '(?i)password':REMOTEPASSWORD + '\n', '(?i)(yes/no) ?':'yes' + '\n' },
                                        withexitstatus = True )
    
##    logging.info( '(%s) DEBUG: output = %s' % (GetCurrentTime(), Output.strip('\n')) )
##    logging.info( '(%s) DEBUG: status = %s' % (GetCurrentTime(), ExitStatus) )

    if ExitStatus == 0:
        logging.info('ssh connection to "%s" established' % REMOTEHOST)
        try:
            SMSsend(SenderNumber,
                    'NOKIA N900\n' + \
                    '%s\n' % GetTime() + \
                    '-ssh-\n' + \
                    'connection established.')
        except:
            # no sendernumber in TEST: ssh
            pass

    else:
        logging.error('ssh connection failed to "%s"' % REMOTEHOST)
        if StatusSSH() == True:
            StopSSH()
        try:
            SMSsend(SenderNumber,
                    'NOKIA N900\n' + \
                    '-ssh-\n' +\
                    'connection failed.')
        except:
            # no sendernumber in TEST: ssh
            pass

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

def StopSSH(Verbose=True):
    """
    Kill reverse-ssh connection to remote host.
    """

    if Verbose == True:
        logging.info('trying to kill ssh')

    PID = os.popen('pidof ssh').read().strip('\n')

    if PID == '':
        if Verbose == True:
            logging.info("ssh wasn't active")
    else:
        os.system('kill $(pidof ssh)')
        if Verbose == True:
            logging.info('ssh is stopped [PID=%s]' % PID)        

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

def StatusSSH():
    """
    Check if ssh is active or not running.
    """

    PID = os.popen('pidof ssh').read().strip('\n')

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

##############################################################
#    gps
##############################################################

def EnableGPStracking(Mode):
    """
    Start the GPS device mainloop.
    """

    global GpsMode
    global GpsActive
    
    GpsMode = Mode
    
    if GpsActive == False:
        GpsActive = True

        # enable GPS device control
        gobject.idle_add(GPSstart, GpsControl)
    else:
        logging.warning('GPS device already active')

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

def DisableGPStracking():
    """
    Stop the GPS device mainloop.
    """

    global GpsActive

    if GpsActive == True:
        GpsActive = False

        # stop the GPS device
        GpsData.stop()
        logging.info('stopped acquiering GPS coordinates')
    else:
        logging.info('GPS device already stopped')

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

def GPSstart(GpsData):
    """
    Start the GPS device.
    """
    
    global StartTime
    global GpsState
    
    GpsState = False

    if GpsMode == 'single':
        logging.info('starting GPS device in location mode')
    elif GpsMode == 'multi':
        logging.info('starting GPS device in tracking mode')
    
    StartTime = time.time()
    GpsData.start()
    
    return False

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

def GPSchanged(GpsDevice, GpsData):
    """
    Get GPS device data.
    """

    global Gps
    global GpsList
    global GPSPOLLING
    global GpsState
    global StartTime

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

    # check GPS device timout
    PastTime = time.time() - StartTime
    if GpsState == False:
        #logging.debug('StartTime = %s' % StartTime)
        if PastTime >= GPSTIMEOUT:
            logging.error('GPS device timeout after %.1f sec' % PastTime)

            # stop the GPS device
            GpsData.stop()

            if GPSSEND == 'sms':
                try:
                    SMSsend(SenderNumber,
                            'NOKIA N900\n' + \
                            '-GPS-\n' + \
                            'failure\n' + \
                            'Search time = %.1f sec.' % PastTime)
                except:
                    logging.error('failed to send GPS timeout failure by %s' % GPSSEND)
                else:
                    logging.info('send GPS timeout failure by %s' % GPSSEND)
            elif GPSSEND == 'email':
                try:
                    EMAILsend(EMAILADDRESS,
                              'From "%s" command / Nokia N900' % COM_TRACKON,
                              'GPS device timeout after %.1f sec' % PastTime)
                except:
                    logging.error('failed to send GPS timeout failure by %s' % GPSSEND)
                else:
                    logging.info('send GPS timeout failure by %s' % GPSSEND)
            return
    else:
        # adjust start time of GPS device
        StartTime = time.time()

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

    # GPS device has fix.
    if GpsDevice.fix[1] & location.GPS_DEVICE_LATLONG_SET:
            
        Latitude  = GpsDevice.fix[4]
        Longitude = GpsDevice.fix[5]
        Accuracy  = GpsDevice.fix[6] / 100

        # no valid coordinate acquired by GPS device
        if (str(Latitude) == 'nan') or (str(Longitude) == 'nan') or (str(Accuracy) == 'nan'):
            GpsState = False
            logging.info('waiting for valid GPS coordinate (time = %.1f sec.)'% PastTime)
            return

        # valid coordinate acquired by GPS device
        else:
            GpsState = True
            # single mode
            if GpsMode == 'single':
                if Gps == 0:
                    logging.info('start collecting GPS coordinates')

                # make GPS coordinate list
                GpsList.append( (Latitude,
                                 Longitude,
                                 Accuracy) )
                logging.info( 'GPS coordinate acquired (#%s, %f, %f, %.1f m.)' % (Gps + 1,
                                                                                  Latitude,
                                                                                  Longitude,
                                                                                  Accuracy) )
                Gps += 1
                if Gps == GPSPOLLING:
                    # sort GPS coordinate list on 'Accuracy'
                    GpsList = sorted( GpsList, key=itemgetter(2) )
                    
                    # get best GPS coordinate
                    LatBest, LongBest, AccBest = GpsList[0]
                    logging.info( 'best accurate GPS coordinate (%f, %f, %.1f m.)' % (LatBest,
                                                                                      LongBest,
                                                                                      AccBest) )
                    GpsText = 'NOKIA N900\n' + \
                              '-GPS-\n' + \
                              'Lat = %f\n' % LatBest + \
                              'Long = %f\n' % LongBest + \
                              'Pos accuracy = %.1f m.\n' % AccBest + \
                              'Search time = %.1f sec.' % PastTime
                     
                    if GPSSEND == 'sms':
                        try:
                            SMSsend(SenderNumber, GpsText)
                        except:
                            logging.error('failed to send GPS coordinate by %s' % GPSSEND)
                        else:
                            logging.info('GPS coordinate send to "%s"' % SenderNumber)

                    elif GPSSEND == 'email':
                        try:
                            if CreateGoogleMap(LatBest, LongBest): 
                                EMAILsend(EMAILADDRESS,
                                          'From "%s" command / Nokia N900' % COM_LOCATION,
                                          GpsText.replace('\n', '<br>'),
                                          LogPath + MapName)
                            else:
                                EMAILsend( EMAILADDRESS,
                                           'From "%s" command / Nokia N900' % COM_LOCATION,
                                           GpsText.replace('\n', '<br>') )                                   
                        except:
                            logging.error('failed to send GPS coordinate by %s' % GPSSEND)
                        else:
                            logging.info('GPS coordinate map send to "%s"' % EMAILADDRESS)    

                    GpsList = []
                    # stop the GPS device
                    GpsData.stop()
                    return
                    
            # tracking mode
            elif GpsMode == 'multi':
                logging.info( 'GPS coordinate acquired (%f, %f, %.1f m.)' % (Latitude,
                                                                             Longitude,
                                                                             Accuracy) )
                GpsText = 'NOKIA N900\n' + \
                          '-GPS-\n' + \
                          'Lat = %f\n' % Latitude + \
                          'Long = %f\n' % Longitude + \
                          'Pos accuracy = %.1f m.\n' % Accuracy + \
                          'Search time = %.1f sec.' % PastTime

                if GPSSEND == 'sms':
                    try:
                        SMSsend(SenderNumber, GpsText)
                    except:
                        logging.error('failed to send GPS coordinate by %s' % GPSSEND)
                    else:
                        logging.info('GPS coordinate send to "%s"' % SenderNumber)

                elif GPSSEND == 'email':
                    try:
                        if CreateGoogleMap(Latitude, Longitude): 
                            EMAILsend(EMAILADDRESS,
                                      'From "%s" sms command / Nokia N900' % COM_TRACKON,
                                      GpsText.replace('\n', '<br>'),
                                      LogPath + MapName)
                        else:
                            EMAILsend( EMAILADDRESS,
                                       'From "%s" sms command / Nokia N900' % COM_TRACKON,
                                       GpsText.replace('\n', '<br>') )                                    
                    except:
                        logging.error('failed to send GPS coordinate by %s' % GPSSEND)
                    else:
                        logging.info('GPS coordinate map send to "%s"' % EMAILADDRESS)    
    else:
        logging.info('waiting for GPS device satellite fix')

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

def GPSerror(GpsControl, GpsError, GpsData):
    """
    GPS device error detected.
    """

    global GpsActive
    
    GpsActive = False

    logging.error('GPS device error (%d)' % GpsError)
    logging.error('stopping failed GPS device')
    GpsData.quit()

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

def GPSstop(GpsControl, GpsData):
    """
    Stop the GPS device.
    """

    global GpsActive
    
    GpsActive = False
    
    logging.info('stopping GPS device')
    GpsData.quit()

##############################################################
#    googlemaps map
##############################################################

def CreateGoogleMap(Latitude, Longitude):
    """
    Create map of current GPS coordinate.
    """

    MapZoom     = 16
    MapSize     = (600,600)
    MarkerColor = 'red'
    MarkerLabel = 'N'

    MapUrl = 'http://maps.google.com/maps/api/staticmap?center=%s,%s&zoom=%s&size=%sx%s&format=png&markers=color:%s|label:%s|%s,%s&sensor=false' \
              % (Latitude,
                 Longitude,
                 MapZoom,
                 MapSize[0],
                 MapSize[1],
                 MarkerColor,
                 MarkerLabel,
                 Latitude,
                 Longitude)

    try:
        urllib.urlretrieve(MapUrl, LogPath + MapName)
    except:
        logging.error('failed to create GPS coordinate map (retrieve error)')
        return False
    else:
        if os.path.isfile(LogPath + MapName) == True:
            logging.info('created GPS coordinate map')
            return True
        else:
            logging.error('failed to create GPS coordinate map (file not found)')
            return False

##############################################################
#    sim card
##############################################################

def ValidateIMSI():
    """
    Check if current used IMSI code from SIM card is authorized.
    """
    
    CurrentIMSI = GetCode('IMSI')

    # no SIM card found
    if CurrentIMSI == None:
        logging.critical('no SIM card present; %s will quit' % DaemonFile)
        quit()

    # compare current IMSI code with saved one in file.
    elif CurrentIMSI == ReadIMSI():
        logging.info('authorized IMSI code found')

    else:
        logging.warning('IMSI code has changed to "%s"' % CurrentIMSI)

        # save new IMSI code to file
        WriteIMSI(CurrentIMSI)    

        # send reply sms
        SMSsend(SenderNumber,
                'NOKIA N900\n' + \
                '%s\n' % GetTime() + \
                '-New SIM card-\n' + \
                'IMSI = %s\n' % CurrentIMSI + \
                'IMEI = %s\n' % GetCode('IMEI') + \
                'Operator = "%s".' % GetOperatorName() )

        logging.info('new SIM number/IMSI code send to "%s"' % SenderNumber)

        # set "Internet connection" behavior
        EnableConnectAutomatically('GPRS')

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

def ReadIMSI():
    """
    Load IMSI code from file.
    """

    if CheckFile(Path, CodeFile) == True:
        f = open(Path + CodeFile, 'r+')
        Code = f.readline().strip('\n')
        f.close()

        logging.info('reading valid IMSI code (%s) from file' % Code)

        return Code
    else:
        logging.warning('initalizing new %s file with current valid IMSI code' % ConfigFile)
    
        CodeValid = GetCode('IMSI')

        # write new file with current valid imsi code
        f = open(Path + CodeFile, 'w')
        f.writelines(CodeValid)
        f.close()

        logging.info('current valid IMSI code (%s) saved to file' % CodeValid)

        return CodeValid

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

def WriteIMSI(Code):
    """
    Save new IMSI code to file.
    """

    if CheckFile(Path, CodeFile) == False:
        logging.error('IMSI file not found; creating new %s file' % ConfigFile)

        f = open(Path + CodeFile, 'w')
        f.writelines(Code)
        f.close()

        logging.info('new IMSI code (%s) saved to file' % Code)        

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

def GetCode(CodeType):
    """
    Get current SIM IMSI code or device hardware IMEI code.
    """

    if CodeType == 'IMEI':
        Command = 'dbus-send --system --type=method_call --print-reply --dest=com.nokia.phone.SIM /com/nokia/phone/SIM/security Phone.Sim.Security.get_imei'
    elif CodeType == 'IMSI':
        Command = 'dbus-send --system --type=method_call --print-reply --dest=com.nokia.phone.SIM /com/nokia/phone/SIM Phone.Sim.get_imsi'

    try:
        Output = os.popen(Command).read()
        E = re.compile('string "(\S+)"')
        R = E.findall(Output)[0]
    except:
        logging.error("couldn't get %s code" % CodeType)
        return None
               
    return R

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

def GetOperatorName():
    """
    Get current SIM telecom operator name.
    """

    try:
        Output = os.popen('dbus-send --system --print-reply=literal --dest=com.nokia.phone.net /com/nokia/phone/net Phone.Net.get_registration_status').read()
        E = re.compile('uint32 (\d+)')
        R = E.findall(Output)
        Output = os.popen( 'dbus-send --system --print-reply --dest=com.nokia.phone.net /com/nokia/phone/net Phone.Net.get_operator_name byte:0 uint32:"%s" uint32:"%s"' % (R[1], R[2]) ).read()
        E = re.compile('string "(.*)"')
        R = E.findall(Output)[0]
    except:
        logging.error("couldn't get operator name")
        return 'UNKNOWN'        

    return R

##############################################################
#   arguments
##############################################################

def CheckArguments():
    """
    Check arguments for smscon_daemon.
    """    

    # check for arguments
    if sys.argv[1:] != []:
        NumArgs = len(sys.argv[1:])

        if NumArgs == 2:
            Mode = sys.argv[1]
            
            if Mode == '-test':
                Func = sys.argv[2]
                
                if Func == 'gps1' or Func == 'gps2': # test mode 1 & 2 for gps
                    logging.info( '%s TEST' % Func.upper() )
                    EnableConnection('WLAN')
                    if Func == 'gps1':
                        EnableGPStracking('single')
                    elif Func == 'gps2':
                        EnableGPStracking('multi')

                elif Func == 'ssh': # test mode for ssh connection
                    logging.info( '%s TEST' % Func.upper() )
                    if StatusSSH() == True:
                        StopSSH(False)
                    EnableConnection('GPRS')
                    EnableSSHcontrol()

                elif Func == 'cam': # test mode for camera
                    logging.info( '%s TEST' % Func.upper() )
                    EnableCamera()
                    
                elif Func == 'sms': # test mode for sms
                    logging.info( '%s TEST' % Func.upper() )
                    SMSsend(SENDERNUMBER, 'SMS TEST')
                    logging.info('TEST: send test sms message to "%s"' % SENDERNUMBER)

                elif Func == 'email1' or Func == 'email2' : # test mode 1 & 2 for email
                    logging.info( '%s TEST' % Func.upper() )
                    EnableConnection('GPRS')
                    if Func == 'email1':
                        EMAILsend(EMAILADDRESS,
                                  'Test email from Nokia N900 device',
                                  'Email sending from Nokia N900 device is succesfull.')
                    elif Func == 'email2':
                        if TakeCamPicture() == True:
                            EMAILsend(EMAILADDRESS,
                                      'Test email from Nokia N900 device',
                                      'This is a frontcam picture from Nokia N900 device:',
                                      LogPath + PhotoName)

                elif Func == 'call': # test mode for phone call
                    logging.info( '%s TEST' % Func.upper() )
                    MakePhonecall()

                else:
                    logging.error('test option error (%s)' % Func)
                    sys.exit(1)

            else:
                logging.error('test option error (%s)' % Mode)
                sys.exit(1)                

##############################################################
#   autoloader
##############################################################

def CheckAutoloader():
    """
    Check if smscon autoloads at system boot.
    """

    if CheckFile(BootPath, BootFile) == True:
        logging.info('smscon autoloads at boot')
    else:
        logging.warning("smscon doesn't autoload at boot")

##############################################################
#   keyboard detect
##############################################################

def KEYBOARDslider(Action, Type):
    """
    Check state change of keyboard slider.
    """

    KeyState = os.popen('cat /sys/devices/platform/gpio-switch/slide/state').read().strip('\n')

    # keyboard slider: 'open to closed' state or 'closed to open' state
    if KeyState == 'closed' or KeyState == 'open':
        #logging.debug(' Keyboard is used (%s) / (%s)' % (KeyState, EnableKeyboardDetect) )
        if EnableKeyboardDetect == True:
            try:
                SMSsend(SenderNumber,
                        'NOKIA N900\n' + \
                        '%s\n' % GetTime() + \
                        '-Keyboard-\n' + \
                        'Phone is being used:\n' + \
                        'keyboard is %s.' % KeyState)           
            except:
                logging.error('keyboard used sms failed to send to "%s"' % SenderNumber)
            else:
                logging.info('send keyboard used sms to "%s"' % SenderNumber)

##############################################################
#   check file
##############################################################

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

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

##############################################################
#    MAIN
##############################################################

# load user settings from smscon_config file
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',
              'USER',         
              'PASSWORD',        
              'EMAILFROM',       
              'MAILSERVER',      
              'MAILPORT',        
              'REMOTEHOST',            
              'REMOTEPORT',           
              'REMOTEUSER',       
              'REMOTEPASSWORD',
              'COMMANDREPLY',
              'KEYBOARDDETECT',
              'AUTODEVICELOCK',
              'GPSTIMEOUT',
              'GPSPOLLING',
              'GPSINTERVAL',
              'GPSSEND']
try:
    f = open(Path + ConfigFile, 'r')
    f.close()
except:
    logging.warning("%s doesn't excist" % ConfigFile)
    quit()
else:
    f = open(Path + ConfigFile, 'r')
    ConfigLines = f.readlines() 
    if ConfigLines == []:
        logging.critical('%s is empty.' % ConfigFile)
        quit()
    else:
        try:
            for Line in ConfigLines:
                Line = Line.strip().replace('\r', '').replace('\n', '')
                
                for Var in ConfigVars:
                    # get user variable & it's value from text line
                    if Line.startswith('%s' % Var):
                        try:
                            Variable, Value = Line.split('=')
                        except:
                            logging.critical( 'syntax error in "%s" file (%s)' % (ConfigFile, Line) )
                            quit()
                        else:
                            Value = Value.lstrip(' ')
                            try:
                                if Value.startswith("'") == True and Value.endswith("'") == True:
                                    vars()[Var] = Value.strip("'") # get string
                                else:
                                    vars()[Var] = int(Value) # get integer
                            except:
                                logging.critical( 'syntax error in "%s" file (%s)' % (ConfigFile, Line) )
                                quit()
                                
            # set reply sms number
            SenderNumber = SENDERNUMBER
        except:
            logging.critical('error reading "%s" file' % ConfigFile)
            quit()
        else:
            logging.info('succesfully loaded "%s" file' % ConfigFile)            
            for UserVar in ConfigVars:
                Name  = UserVar
                Value = vars()[UserVar]

                if isinstance(Value, int) == True:
                    Type = 'INT'
                elif isinstance(Value, str) == True:
                    Type = 'STR'
                else:
                    Type = 'UNKNOWN'
                
                logging.info( '%s = [%s]' % (UserVar, Value) )
    f.close()

ValidateIMSI()
CheckAutoloader()

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

try:            
    DBusGMainLoop(set_as_default = True)

    bus = dbus.SystemBus()

    # set battery charge measurment
    hal_obj = bus.get_object('org.freedesktop.Hal',
                             '/org/freedesktop/Hal/Manager')

    hal = dbus.Interface(hal_obj,
                         'org.freedesktop.Hal.Manager')

    uids = hal.FindDeviceByCapability('battery')
    dev_obj = bus.get_object('org.freedesktop.Hal',
                             uids[0])

    # set GPS device control
    GpsControl = location.GPSDControl.get_default()
    GpsDevice  = location.GPSDevice()

    # set GPS method & interval
    if   GPSINTERVAL == '10':
        GpsControl.set_properties(preferred_method   = location.METHOD_GNSS|location.METHOD_AGNSS,
                                  preferred_interval = location.INTERVAL_10S)
    elif GPSINTERVAL == '20':
        GpsControl.set_properties(preferred_method   = location.METHOD_GNSS|location.METHOD_AGNSS,
                                  preferred_interval = location.INTERVAL_20S)
    elif GPSINTERVAL == '30':
        GpsControl.set_properties(preferred_method   = location.METHOD_GNSS|location.METHOD_AGNSS,
                                  preferred_interval = location.INTERVAL_30S)
    elif GPSINTERVAL == '60':
        GpsControl.set_properties(preferred_method   = location.METHOD_GNSS|location.METHOD_AGNSS,
                                  preferred_interval = location.INTERVAL_60S)
    elif GPSINTERVAL == '120':
        GpsControl.set_properties(preferred_method   = location.METHOD_GNSS|location.METHOD_AGNSS,
                                  preferred_interval = location.INTERVAL_120S)
    else:
        GpsControl.set_properties(preferred_method   = location.METHOD_GNSS|location.METHOD_AGNSS,
                                  preferred_interval = location.INTERVAL_60S)       
     
    GpsControl.connect( 'error-verbose', GPSerror, gobject.MainLoop() )
    GpsDevice.connect('changed', GPSchanged, GpsControl)
    GpsControl.connect( 'gpsd-stopped', GPSstop, gobject.MainLoop() )

    # set SMS signal receiver
    bus.add_signal_receiver(SMSreceive,
                            path           = '/com/nokia/phone/SMS',
                            dbus_interface = 'Phone.SMS',
                            signal_name    = 'IncomingSegment')

    # set keyboard slider signal receiver
    bus.add_signal_receiver(KEYBOARDslider,
                            path           = '/org/freedesktop/Hal/devices/platform_slide',
                            dbus_interface = 'org.freedesktop.Hal.Device',
                            signal_name    = 'Condition')
    CheckArguments()

    gobject.MainLoop().run()

except Exception, e:
    logging.critical('<<< fatal error >>>\n%s' % e)
    print '\n<<< SMSCON FATAL ERROR >>>\n%s' % e
    
    gobject.MainLoop().quit()

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


# FIX: always enable GPRS connection before using GPS.
# FIX: METHOD_AGNSS to GNSS bug.
# FIX: Trackon / Trackoff works now.
# FIX: sms command can use spaces now.
# FIX: smscon_boot loads at device boot.
# NEW: keyboard detect function.
# FIX: when GPS is active all other program events are on hold/discarted.

