Capisuite

Aus Neobikers Wiki
Zur Navigation springen Zur Suche springen

Capisuite Erweiterung

Bei der Umstellung von VBox unter Debian Sarge auf Capisuite unter Etch habe ich schmerzlich die Möglichkeit der individuellen Programmierung der Anrufbeantworter vermisst. Nachdem ich keine verbesserten Incoming.py Files im Web gefunden habe, habe ich auf die schnelle Python gelernt und die fehlende Funktion selbst implementiert.

Individuelle Programmierung

  • Die Programme werden fortlaufend (!) nummeriert, beginnend bei prog1
  • Das erste Programm prog1 ... progX das match'ed wird verwendet, deshalb ist die Reihenfolge relevant!
  • Die Einträge dates und group können als eigene Section definiert werden (siehe holiday, family, friends)
  • Die Telefonnummern müssen so definiert sein, wie sie im Logfile (/var/log/capisuite.log) erscheinen, d.h. üblicherweise 0911 12345678 (nicht: +49 (911) 12345678).


Mein Konfig-File /etc/capisuite/answering_machine.conf sieht in etwa wie folgt aus und sollte weitestgehend selbsterklärend sein.

[GLOBAL]
audio_dir="/usr/share/capisuite/"
voice_user_dir="/var/spool/capisuite/users/"
user_audio_files="1"
voice_delay="15"
announcement="ab_buis.la"
record_length="60"
record_silence_timeout="5"
voice_email_from="Anrufbeantworter <ab@neobiker.de>"

[priv]
voice_numbers="12345678"
announcement="ab_priv.la"
voice_action="MailAndSave"
voice_delay="10"
record_length="90"
voice_email_from="ab@neobiker.de"
voice_email="Anrufbeantworter (Priv) <ab@neobiker.de>"
pin="1111"
# ----- vbox programming:
# ----- dates   time frame      week days       file to play    delay   record  group
prog1=  *       22:00-07:00     *               ab_priv.la      5       120     family,friends
prog2=  *       *               *               ab_priv.la      15      120     family,friends
prog3=  *       22:00-07:00     *               ab_priv.la      5       120     *
prog4=  *       *               *               ab_priv.la      10      120     *

[buis]
voice_numbers="22233345"
announcement="ab_buis.la"
voice_action="MailAndSave"
voice_delay="10"
record_length="60"
voice_email_from="Anrufbeantworter (Buis) <ab@neobiker.de>"
voice_email="ab@neobiker.de"
pin="2222"
# ----- vbox programming:
# ----- dates   time frame      week days       file to play    delay   record  group
prog1=  *       22:00-07:00     *               ab_priv.la      5       120     family,friends
prog2=  *       *               *               ab_priv.la      15      120     family,friends
prog3=  holiday *               *               ab_buis.la      1       90      *
prog4=  *       *               SA,SO           ab_buis.la      1       90      *
prog5=  *       17-08:29        *               ab_buis.la      1       90      *
prog6=  *       *               *               ab_buis.la      10      90      *

[holiday]
Weihnachten=24.12.,25.12.,26.12.
Ostern=6.4.2007,9.4.2007,17.5.2007,28.5.2007.,7.6.2007
Sonstige=1.1.,6.1.,1.5.,3.10.,1.11.
Birthdays=19.5.

[family]
Eltern=01234 12345, 0175123456,0172 234567
Oma=123456789

[friends]
Karl=12345,017134566789
Peter=0170123456

Erweiterte Scripts

Ich habe die Files

  • incoming_adv.py
  • idle_adv.py

erstellt. Diese müssen in /etc/capisuite/capisuite.conf aktiviert werden.

Tip:

Folgendes Script compiliert die Python Scripts:

#!/bin/sh
PYTHON=python2.3
DIRLIST=" /usr/lib/capisuite "
for i in $DIRLIST ; do
  $PYTHON -O /usr/lib/$PYTHON/compileall.py -q $i;
  $PYTHON /usr/lib/$PYTHON/compileall.py -q $i;
done

File: /usr/lib/capisuite/incoming_adv.py

#              incoming_adv.py - advanced incoming script for capisuite
#              --------------------------------------------------------
#    copyright            : (c) 2007 by Neobiker
#
#    original script:
#    copyright            : (C) 2002 by Gernot Hillier
#    email                : gernot@hillier.de
#    version              : $Revision: 1.9.2.1 $
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#

# general imports
import datetime,time,os,re,string,pwd
# CapiSuite imports
import capisuite,cs_helpers

# @brief check if user program date fits actual date
#
# @param pdate user program date
# @param adate actual date

def check_date (pdate,adate):
    """check_date() check if actual date fits user program date"""
    if pdate == '*':
        return True
    # set day/month/year from actual date if field is not defined
    idate=pdate.split('.')
    pdate=adate[:]
    # set pdate fields day/month/year as defined in user program
    for i in range(len(idate)):
        if idate[i] and idate[i] != '*': pdate[i] = int(idate[i])
    if pdate == adate:
        if adv_debug:
            print "check_date(): " , pdate , " / " , adate
        return True
    return False

# @brief check if date is listed in date section
#
# @param dsect date section list of dates

def check_date_section (dsect,adate):
    """check_date_section () check if date is listed in date section"""
    for i in range(len(config.items(dsect))):
        for pdate in config.items(dsect)[i][1].split(','):
            if check_date(pdate,adate):
                if adv_debug:
                    print "check_date_Section(): found"
                return True
    return False

# @brief check if actual time fits user program time interval
#
# @param ptime program time intervall
# @param atime actual time

def prog_time (ptime,atime):
    """prog_time() check if actual time fits user program time interval"""
    if ptime == '*':
        return True
    # From: time intervall start
    f0=ptime.split('-')[0] + ':00'
    f=[int(f0.split(':')[0]), int(f0.split(':')[1])]
    # To: time intervall end
    t0=ptime.split('-')[1] + ':00'
    t=[int(t0.split(':')[0]), int(t0.split(':')[1])]
    # actual time is between From - To Intervall?
    if adv_debug:
            print "Time test: ", f, " <= ", atime, " <= ", t, " ?"
    # 1.st test: f < t (means: t < 23:59)
    # 2.nd test: f > t (means: t >= 00:00 -> on next day!)
    if f < t:
        return f <= atime <= t
    else:
        return not (t <= atime <= f)

# @brief check if weekday is listed in user program
#
# @param pwday program weekday
# @param wday  actual weekday

def prog_wday (pwday,wday):
    """prog_wday() check if weekday is listed in user program"""
    if pwday == '*':
        return True
    wd={'MO': 0, 'DI': 1, 'MI': 2, 'DO': 3, 'FR': 4, 'SA': 5, 'SO': 6, \
                 'TU': 1, 'WE': 2, 'TH': 3,                   'SU': 6}
    return wday==wd[pwday.upper()]

# @brief check if call_from is listed in callers section
#
# @param callers list of numbers or sections

def check_caller_section (csect):
    """check_caller_section () check if call_from is listed in callers section"""
    for i in range(len(config.items(csect))):
        for j in config.items(csect)[i][1].split(','):
            if call_from == re.sub('[\s+()-]','',j):
                return True
    return False

# @brief check if call_from is listed in callers list/section
#
# @param callers list of numbers or sections

def check_caller (callers):
    """prog_caller () check if caller_from matches caller entries/section"""
    if callers == '*':
        return True
    for i in range(len(callers.split(','))):
        pcaller=re.sub('[\s+()-]','',callers.split(',')[i])
        if pcaller in config.sections():
            if check_caller_section(pcaller):
                return True
        elif call_from == pcaller:
                return True
    return False

# @brief check if given user program is active
#
# Check if Date/Time/Weekday fits actual date/time
# to see if user program is activ
#
# @param dates program dates
# @param times program time intervals
# @param wdays program week days
# @param d actual date/time

def prog_active (dates, times, wdays, d):
    """prog_active() check if given user program is active"""
    #
    # check the dates in user program
    found=False
    for i in range(len(dates.split(','))):
        pdate=dates.split(',')[i]
        if pdate in config.sections():
            if check_date_section(pdate,[d.day, d.month, d.year]):
                found=True
                break
        elif check_date(pdate,[d.day, d.month, d.year]):
            found=True
            break
    # date didn't fit
    if not found: return False
    #
    # check the times in user program
    found=False
    for i in range(len(times.split(','))):
        ptime=times.split(',')[i]
        if prog_time(ptime,[d.hour, d.minute]):
            found=True
            break
    # time intervall didn't fit
    if not found: return False
    #
    # check the weekdays in user program
    found=False
    for i in range(len(wdays.split(','))):
        pwday=wdays.split(',')[i]
        if prog_wday(pwday,d.weekday()):
            return True
    #
    # weekdays didn't fit
    return False

# @brief read user specific program for vbox
# --- prog[dates, times, weekdays, message, delay, length, callers] ---
#
# It will search for a valid user program (prog#) in the user section
# and reads corresponding definitions for
# - delay    the delay of vbox activation
# - message  the specific message to play
#
# @param (user-)section in config file to read
# @return True/False if valid programm found or not
# @user_prog['delay': xx, 'message': yy]

def read_prog (section):
    """read_prog(section) read user specific program for vbox"""
    prog="prog1"
    d=datetime.datetime.now()
    i=1
    while config.has_option(section,prog):
        prog="prog"+str(i)
        # p[dates, times, weekdays, message, delay, length, callers]
        p=config.get(section,prog).split()
        # check program dates/times/weekdays
        if prog_active(p[0], p[1], p[2] ,d):
            if adv_debug:
                print "prog active: "+prog+" ("+section+")"
            if check_caller(re.sub('[\s+()-]','',p[6])):
                user_prog['user']=section
                user_prog['prog']=i
                user_prog['dates']=p[0]
                user_prog['times']=p[1]
                user_prog['wdays']=p[2]
                user_prog['message']=p[3]
                user_prog['delay']=p[4]
                user_prog['length']=p[5]
                user_prog['callers']=p[6]
                if adv_debug:
                    print "checked: caller("+call_from+") in ", p[6]
                    print user_prog
                return True
        i=i+1
    return False

# @brief main function called by CapiSuite when an incoming call is received
#
# It will decide if this call should be accepted, with which service and for
# which user. The real call handling is done in faxIncoming and voiceIncoming.
#
# @param call reference to the call. Needed by all capisuite functions
# @param service one of SERVICE_FAXG3, SERVICE_VOICE, SERVICE_OTHER
# @param call_from string containing the number of the calling party
# @param call_to string containing the number of the called party

def callIncoming(call,service,call_from,call_to):
        # read sections in config file
        try:
                config=cs_helpers.readConfig()
                userlist=config.sections()
                userlist.remove('GLOBAL')
                user_prog={}
                # debug messages for adv. features
                adv_debug=False
                # search for call_to in the user sections
                curr_user=""
                for u in userlist:
                        if config.has_option(u,'voice_numbers'):
                                numbers=config.get(u,'voice_numbers')
                                if (call_to in numbers.split(',') or numbers=="*"):
                                        if (service==capisuite.SERVICE_VOICE):
                                                curr_user=u
                                                curr_service=capisuite.SERVICE_VOICE
                                                break
                                        if (service==capisuite.SERVICE_FAXG3):
                                                curr_user=u
                                                curr_service=capisuite.SERVICE_FAXG3
                                                break

                        if config.has_option(u,'fax_numbers'):
                                numbers=config.get(u,'fax_numbers')
                                if (call_to in numbers.split(',') or numbers=="*"):
                                        if (service in (capisuite.SERVICE_FAXG3,capisuite.SERVICE_VOICE)):
                                                curr_user=u
                                                curr_service=capisuite.SERVICE_FAXG3
                                                break

        except IOError,e:
                capisuite.error("Error occured during config file reading: "+e+" Disconnecting...")
                capisuite.reject(call,0x34A9)
                return
        # answer the call with the right service
        if (curr_user==""):
                capisuite.log("call from "+call_from+" to "+call_to+" ignoring",1,call)
                capisuite.reject(call,1)
                return
        try:
                if (curr_service==capisuite.SERVICE_VOICE):
                        delay=cs_helpers.getOption(config,curr_user,"voice_delay")
                        active_user_prog=read_prog(curr_user)
                        if active_user_prog:
                            delay=user_prog['delay']
                        if (delay==None):
                                capisuite.error("voice_delay not found for user "+curr_user+"! -> rejecting call")
                                capisuite.reject(call,0x34A9)
                                return
                        capisuite.log("call from "+call_from+" to "+call_to+" for "+curr_user+" connecting with voice",1,call)
                        capisuite.connect_voice(call,int(delay))
                        voiceIncoming(call,call_from,call_to,curr_user,config)
                elif (curr_service==capisuite.SERVICE_FAXG3):
                        faxIncoming(call,call_from,call_to,curr_user,config,0)
        except capisuite.CallGoneError: # catch exceptions from connect_*
                (cause,causeB3)=capisuite.disconnect(call)
                capisuite.log("connection lost with cause 0x%x,0x%x" % (cause,causeB3),1,call)

# @brief called by callIncoming when an incoming fax call is received
#
# @param call reference to the call. Needed by all capisuite functions
# @param call_from string containing the number of the calling party
# @param call_to string containing the number of the called party
# @param curr_user name of the user who is responsible for this
# @param config ConfigParser instance holding the config data
# @param already_connected 1 if we're already connected (that means we must switch to fax mode)
def faxIncoming(call,call_from,call_to,curr_user,config,already_connected):
        try:
                udir=cs_helpers.getOption(config,"","fax_user_dir")
                if (udir==None):
                        capisuite.error("global option fax_user_dir not found! -> rejecting call")
                        capisuite.reject(call,0x34A9)
                        return
                udir=os.path.join(udir,curr_user)+"/"
                if (not os.access(udir,os.F_OK)):
                        userdata=pwd.getpwnam(curr_user)
                        os.mkdir(udir,0700)
                        os.chown(udir,userdata[2],userdata[3])
                if (not os.access(udir+"received/",os.F_OK)):
                        userdata=pwd.getpwnam(curr_user)
                        os.mkdir(udir+"received/",0700)
                        os.chown(udir+"received/",userdata[2],userdata[3])
        except KeyError:
                capisuite.error("user "+curr_user+" is not a valid system user. Disconnecting",call)
                capisuite.reject(call,0x34A9)
                return
        filename="" # assure the variable is defined...
        try:
                stationID=cs_helpers.getOption(config,curr_user,"fax_stationID")
                if (stationID==None):
                        capisuite.error("Warning: fax_stationID not found for user "+curr_user+" -> using empty string")
                        stationID=""
                headline=cs_helpers.getOption(config,curr_user,"fax_headline","") # empty string is no problem here
                capisuite.log("call from "+call_from+" to "+call_to+" for "+curr_user+" connecting with fax",1,call)
                if (already_connected):
                        faxInfo=capisuite.switch_to_faxG3(call,stationID,headline)
                else:
                        faxInfo=capisuite.connect_faxG3(call,stationID,headline,0)
                if (faxInfo!=None and faxInfo[3]==1):
                        faxFormat="cff" # color fax
                else:
                        faxFormat="sff" # normal b&w fax
                filename=cs_helpers.uniqueName(udir+"received/","fax",faxFormat)
                capisuite.fax_receive(call,filename)
                (cause,causeB3)=capisuite.disconnect(call)
                capisuite.log("connection finished with cause 0x%x,0x%x" % (cause,causeB3),1,call)

        except capisuite.CallGoneError: # catch this here to get the cause info in the mail
                (cause,causeB3)=capisuite.disconnect(call)
                capisuite.log("connection lost with cause 0x%x,0x%x" % (cause,causeB3),1,call)

        if (os.access(filename,os.R_OK)):
                cs_helpers.writeDescription(filename,
                  "call_from=\""+call_from+"\"\ncall_to=\""+call_to+"\"\ntime=\""
                  +time.ctime()+"\"\ncause=\"0x%x/0x%x\"\n" % (cause,causeB3))
                userdata=pwd.getpwnam(curr_user)
                os.chmod(filename,0600)
                os.chown(filename,userdata[2],userdata[3])
                os.chmod(filename[:-3]+"txt",0600)
                os.chown(filename[:-3]+"txt",userdata[2],userdata[3])

                fromaddress=cs_helpers.getOption(config,curr_user,"fax_email_from","")
                if (fromaddress==""):
                        fromaddress=curr_user
                mailaddress=cs_helpers.getOption(config,curr_user,"fax_email","")
                if (mailaddress==""):
                        mailaddress=curr_user
                action=cs_helpers.getOption(config,curr_user,"fax_action","").lower()
                if (action not in ("mailandsave","saveonly")):
                        capisuite.error("Warning: No valid fax_action definition found for user "+curr_user+" -> assuming SaveOnly")
                        action="saveonly"
                if (action=="mailandsave"):
                        cs_helpers.sendMIMEMail(fromaddress, mailaddress, "Fax received from "+call_from+" to "+call_to, faxFormat,
                          "You got a fax from "+call_from+" to "+call_to+"\nDate: "+time.ctime()+"\n\n"
                          +"See attached file.\nThe original file was saved to file://"+filename+"\n\n", filename)

# @brief called by callIncoming when an incoming voice call is received
#
# @param call reference to the call. Needed by all capisuite functions
# @param call_from string containing the number of the calling party
# @param call_to string containing the number of the called party
# @param curr_user name of the user who is responsible for this
# @param config ConfigParser instance holding the config data
def voiceIncoming(call,call_from,call_to,curr_user,config):
        try:
                udir=cs_helpers.getOption(config,"","voice_user_dir")
                if (udir==None):
                        capisuite.error("global option voice_user_dir not found! -> rejecting call")
                        capisuite.reject(call,0x34A9)
                        return
                udir=os.path.join(udir,curr_user)+"/"
                if (not os.access(udir,os.F_OK)):
                        userdata=pwd.getpwnam(curr_user)
                        os.mkdir(udir,0700)
                        os.chown(udir,userdata[2],userdata[3])
                if (not os.access(udir+"received/",os.F_OK)):
                        userdata=pwd.getpwnam(curr_user)
                        os.mkdir(udir+"received/",0700)
                        os.chown(udir+"received/",userdata[2],userdata[3])
        except KeyError:
                capisuite.error("user "+curr_user+" is not a valid system user. Disconnecting",call)
                capisuite.reject(call,0x34A9)
                return
        filename=cs_helpers.uniqueName(udir+"received/","voice","la")
        action=cs_helpers.getOption(config,curr_user,"voice_action","").lower()
        if (action not in ("mailandsave","saveonly","none")):
                capisuite.error("Warning: No valid voice_action definition found for user "+curr_user+" -> assuming SaveOnly")
                action="saveonly"
        try:
                capisuite.enable_DTMF(call)
                if not active_user_prog:
                    userannouncement=udir+cs_helpers.getOption(config,curr_user,"announcement","announcement.la")
                else:
                    userannouncement=udir+user_prog['message']
                pin=cs_helpers.getOption(config,curr_user,"pin","")
                if (os.access(userannouncement,os.R_OK)):
                        capisuite.audio_send(call,userannouncement,1)
                else:
                        if (call_to!="-"):
                                capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"anrufbeantworter-von.la"),1)
                                cs_helpers.sayNumber(call,call_to,curr_user,config)
                        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"bitte-nachricht.la"),1)

                if (action!="none"):
                        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la"),1)
                        length=cs_helpers.getOption(config,curr_user,"record_length","60")
                        silence_timeout=cs_helpers.getOption(config,curr_user,"record_silence_timeout","5")
                        capisuite.audio_receive(call,filename,int(length), int(silence_timeout),1)

                dtmf_list=capisuite.read_DTMF(call,0)
                if (dtmf_list=="X"):
                        if (os.access(filename,os.R_OK)):
                                os.unlink(filename)
                        faxIncoming(call,call_from,call_to,curr_user,config,1)
                elif (dtmf_list!="" and pin!=""):
                        dtmf_list+=capisuite.read_DTMF(call,3) # wait 5 seconds for input
                        count=1
                        while (count<3 and pin!=dtmf_list):  # try again if input was wrong
                                capisuite.log("wrong PIN entered...",1,call)
                                capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la"))
                                dtmf_list=capisuite.read_DTMF(call,3)
                                count+=1
                        if (pin==dtmf_list):
                                if (os.access(filename,os.R_OK)):
                                        os.unlink(filename)
                                capisuite.log("Starting remote inquiry...",1,call)
                                remoteInquiry(call,udir,curr_user,config)

                (cause,causeB3)=capisuite.disconnect(call)
                capisuite.log("connection finished with cause 0x%x,0x%x" % (cause,causeB3),1,call)

        except capisuite.CallGoneError: # catch this here to get the cause info in the mail
                (cause,causeB3)=capisuite.disconnect(call)
                capisuite.log("connection lost with cause 0x%x,0x%x" % (cause,causeB3),1,call)

        if (os.access(filename,os.R_OK)):
                cs_helpers.writeDescription(filename,
                  "call_from=\""+call_from+"\"\ncall_to=\""+call_to+"\"\ntime=\""
                  +time.ctime()+"\"\ncause=\"0x%x/0x%x\"\n" % (cause,causeB3))
                userdata=pwd.getpwnam(curr_user)
                os.chmod(filename,0600)
                os.chown(filename,userdata[2],userdata[3])
                os.chmod(filename[:-2]+"txt",0600)
                os.chown(filename[:-2]+"txt",userdata[2],userdata[3])

                fromaddress=cs_helpers.getOption(config,curr_user,"voice_email_from","")
                if (fromaddress==""):
                        fromaddress=curr_user
                mailaddress=cs_helpers.getOption(config,curr_user,"voice_email","")
                if (mailaddress==""):
                        mailaddress=curr_user
                if (action=="mailandsave"):
                        cs_helpers.sendMIMEMail(fromaddress, mailaddress, "Voice call received from "+call_from+" to "+call_to, "la",
                          "You got a voice call from "+call_from+" to "+call_to+"\nDate: "+time.ctime()+"\n\n"
                          +"See attached file.\nThe original file was saved to file://"+filename+"\n\n", filename)


# @brief remote inquiry function (uses german wave snippets!)
#
# commands for remote inquiry
# delete message - 1
# next message - 4
# last message - 5
# repeat current message - 6
#
# @param call reference to the call. Needed by all capisuite functions
# @param userdir spool_dir of the current_user
# @param curr_user name of the user who is responsible for this
# @param config ConfigParser instance holding the config data
def remoteInquiry(call,userdir,curr_user,config):
        import time,fcntl,errno,os
        # acquire lock
        lockfile=open(userdir+"received/inquiry_lock","w")
        try:
                fcntl.lockf(lockfile,fcntl.LOCK_EX | fcntl.LOCK_NB) # only one inquiry at a time!

        except IOError,err: # can't get the lock
                if (err.errno in (errno.EACCES,errno.EAGAIN)):
                        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"fernabfrage-aktiv.la"))
                        lockfile.close()
                        return

        try:
                # read directory contents
                messages=os.listdir(userdir+"received/")
                messages=filter (lambda s: re.match("voice-.*\.la",s),messages)  # only use voice-* files
                messages=map(lambda s: int(re.match("voice-([0-9]+)\.la",s).group(1)),messages) # filter out numbers
                messages.sort()

                # read the number of the message heard last at the last inquiry
                lastinquiry=-1
                if (os.access(userdir+"received/last_inquiry",os.W_OK)):
                        lastfile=open(userdir+"received/last_inquiry","r")
                        lastinquiry=int(lastfile.readline())
                        lastfile.close()

                # sort out old messages
                oldmessages=[]
                i=0
                while (i<len(messages)):
                        if (messages[i]<=lastinquiry):
                                oldmessages.append(messages[i])
                                del messages[i]
                        else:
                                i+=1

                cs_helpers.sayNumber(call,str(len(messages)),curr_user,config)
                if (len(messages)==1):
                        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-nachricht.la"),1)
                else:
                        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-nachrichten.la"),1)

                # menu for record new announcement
                cmd=""
                while (cmd not in ("1","9")):
                        if (len(messages)+len(oldmessages)):
                                capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"zum-abhoeren-1.la"),1)
                        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"fuer-neue-ansage-9.la"),1)
                        cmd=capisuite.read_DTMF(call,0,1)
                if (cmd=="9"):
                        newAnnouncement(call,userdir,curr_user,config)
                        return

                # start inquiry
                for curr_msgs in (messages,oldmessages):
                        cs_helpers.sayNumber(call,str(len(curr_msgs)),curr_user,config)
                        if (curr_msgs==messages):
                                if (len(curr_msgs)==1):
                                        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-nachricht.la"),1)
                                else:
                                        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-nachrichten.la"),1)
                        else:
                                if (len(curr_msgs)==1):
                                        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"nachricht.la"),1)
                                else:
                                        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"nachrichten.la"),1)

                        i=0
                        while (i<len(curr_msgs)):
                                filename=userdir+"received/voice-"+str(curr_msgs[i])+".la"
                                descr=cs_helpers.readConfig(filename[:-2]+"txt")
                                capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"nachricht.la"),1)
                                cs_helpers.sayNumber(call,str(i+1),curr_user,config)
                                capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"von.la"),1)
                                cs_helpers.sayNumber(call,descr.get('GLOBAL','call_from'),curr_user,config)
                                capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"fuer.la"),1)
                                cs_helpers.sayNumber(call,descr.get('GLOBAL','call_to'),curr_user,config)
                                capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"am.la"),1)
                                calltime=time.strptime(descr.get('GLOBAL','time'))
                                cs_helpers.sayNumber(call,str(calltime[2]),curr_user,config)
                                capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"..la"),1)
                                cs_helpers.sayNumber(call,str(calltime[1]),curr_user,config)
                                capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"..la"),1)
                                capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"um.la"),1)
                                cs_helpers.sayNumber(call,str(calltime[3]),curr_user,config)
                                capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"uhr.la"),1)
                                cs_helpers.sayNumber(call,str(calltime[4]),curr_user,config)
                                capisuite.audio_send(call,filename,1)
                                cmd=""
                                while (cmd not in ("1","4","5","6")):
                                        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"erklaerung.la"),1)
                                        cmd=capisuite.read_DTMF(call,0,1)
                                if (cmd=="1"):
                                        os.remove(filename)
                                        os.remove(filename[:-2]+"txt")
                                        del curr_msgs[i]
                                        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"nachricht-geloescht.la"))
                                elif (cmd=="4"):
                                        if (curr_msgs[i]>lastinquiry):
                                                lastinquiry=curr_msgs[i]
                                                lastfile=open(userdir+"received/last_inquiry","w")
                                                lastfile.write(str(curr_msgs[i])+"\n")
                                                lastfile.close()
                                        i+=1
                                elif (cmd=="5"):
                                        i-=1
                capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"keine-weiteren-nachrichten.la"))

        finally:
                # unlock
                fcntl.lockf(lockfile,fcntl.LOCK_UN)
                lockfile.close()
                os.unlink(userdir+"received/inquiry_lock")

# @brief remote inquiry: record new announcement (uses german wave snippets!)
#
# @param call reference to the call. Needed by all capisuite functions
# @param userdir spool_dir of the current_user
# @param curr_user name of the user who is responsible for this
# @param config ConfigParser instance holding the config data
def newAnnouncement(call,userdir,curr_user,config):
        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"bitte-neue-ansage-komplett.la"))
        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la"))
        cmd=""
        while (cmd!="1"):
                capisuite.audio_receive(call,userdir+"announcement-tmp.la",60,3)
                capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-ansage-lautet.la"))
                capisuite.audio_send(call,userdir+"announcement-tmp.la")
                capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"wenn-einverstanden-1.la"))
                cmd=capisuite.read_DTMF(call,0,1)
                if (cmd!="1"):
                        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"bitte-neue-ansage-kurz.la"))
                        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la"))
        userannouncement=userdir+cs_helpers.getOption(config,curr_user,"announcement","announcement.la")
        os.rename(userdir+"announcement-tmp.la",userannouncement)
        userdata=pwd.getpwnam(curr_user)
        os.chown(userannouncement,userdata[2],userdata[3])

        capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"ansage-gespeichert.la"))

#
# History:
#
# $Log: incoming.py,v $
# Revision 1.9.2.1  2003/08/24 12:47:19  gernot
# - faxIncoming tried to reconnect when it was called after a switch from
#   voice to fax mode, which lead to a call abort. Thx to Harald Jansen &
#   Andreas Scholz for reporting!
#
# Revision 1.9  2003/06/27 07:51:09  gernot
# - replaced german umlaut in filename "nachricht-gelscht.la", can cause
#   problems on Redhat, thx to Herbert Hübner for reporting
#
# Revision 1.8  2003/06/16 10:21:05  gernot
# - define filename in any case (thx to Axel Schneck for reporting and
#   analyzing...)
#
# Revision 1.7  2003/05/25 13:38:30  gernot
# - support reception of color fax documents
#
# Revision 1.6  2003/04/10 21:29:51  gernot
# - support empty destination number for incoming calls correctly (austrian
#   telecom does this (sic))
# - core now returns "-" instead of "??" for "no number available" (much nicer
#   in my eyes)
# - new wave file used in remote inquiry for "unknown number"
#
# Revision 1.5  2003/03/20 09:12:42  gernot
# - error checking for reading of configuration improved, many options got
#   optional, others produce senseful error messages now if not found,
#   fixes bug# 531, thx to Dieter Pelzel for reporting
#
# Revision 1.4  2003/03/13 11:08:06  gernot
# - fix remote inquiry locking (should fix bug #534, but doesn't - anyway,
#   this fix is definitely necessary)
# - stricter permissions of saved files and created dirs, fixes #544
# - add "file://" prefix to the path shown in the mails to the user
#
# Revision 1.3  2003/02/21 13:13:34  gernot
# - removed some debug output (oops...)
#
# Revision 1.2  2003/02/21 11:02:17  gernot
# - removed os.setuid() from incoming script
#   -> fixes Bug #527
#
# Revision 1.1.1.1  2003/02/19 08:19:54  gernot
# initial checkin of 0.4
#
# Revision 1.11  2003/02/17 11:13:43  ghillie
# - remoteinquiry supports new and old messages now
#
# Revision 1.10  2003/02/03 14:50:08  ghillie
# - fixed small typo
#
# Revision 1.9  2003/01/31 16:32:41  ghillie
# - support "*" for "all numbers"
# - automatic switch voice->fax when SI says fax
#
# Revision 1.8  2003/01/31 11:24:41  ghillie
# - wrong user handling for more than one users fixed
# - creates user_dir/user and user_dir/user/received now separately as
#   idle.py can also create user_dir/user now
#
# Revision 1.7  2003/01/27 21:57:54  ghillie
# - fax_numbers and voice_numbers may not exist (no fatal error any more)
# - accept missing email option
# - fixed typo
#
# Revision 1.6  2003/01/27 19:24:29  ghillie
# - updated to use new configuration files for fax & answering machine
#
# Revision 1.5  2003/01/19 12:03:27  ghillie
# - use capisuite log functions instead of stdout/stderr
#
# Revision 1.4  2003/01/17 15:09:49  ghillie
# - cs_helpers.sendMail was renamed to sendMIMEMail
#
# Revision 1.3  2003/01/16 12:58:34  ghillie
# - changed DTMF timeout for pin to 3 seconds
# - delete recorded wave if fax or remote inquiry is recognized
# - updates in remoteInquiry: added menu for recording own announcement
# - fixed some typos
# - remoteInquiry: delete description file together with call if requested
# - new function: newAnnouncement
#
# Revision 1.2  2003/01/15 15:55:12  ghillie
# - added exception handler in callIncoming
# - faxIncoming: small typo corrected
# - voiceIncoming & remoteInquiry: updated to new config file system
#
# Revision 1.1  2003/01/13 16:12:58  ghillie
# - renamed from incoming.pyin to incoming.py as all previously processed
#   variables are moved to config and cs_helpers.pyin
#
# Revision 1.4  2002/12/18 14:34:56  ghillie
# - added some informational prints
# - accept voice calls to fax nr
#
# Revision 1.3  2002/12/16 15:04:51  ghillie
# - added missing path prefix to delete routing in remote inquiry
#
# Revision 1.2  2002/12/16 13:09:25  ghillie
# - added some comments about the conf_* vars
# - added conf_wavedir
# - added support for B3 cause now returned by disconnect()
# - corrected some dir entries to work in installed system
#
# Revision 1.1  2002/12/14 13:53:18  ghillie
# - idle.py and incoming.py are now auto-created from *.pyin
#
# Revision 1.4  2002/12/11 12:58:05  ghillie
# - read return value from disconnect()
# - added disconnect() to exception handler
#
# Revision 1.3  2002/12/09 15:18:35  ghillie
# - added disconnect() in exception handler
#
# Revision 1.2  2002/12/02 21:30:42  ghillie
# fixed some minor typos
#
# Revision 1.1  2002/12/02 21:15:55  ghillie
# - moved scripts to own directory
# - added remote-connect script to repository
#
# Revision 1.20  2002/12/02 20:59:44  ghillie
# another typo :-|
#
# Revision 1.19  2002/12/02 20:54:07  ghillie
# fixed small typo
#
# Revision 1.18  2002/12/02 16:51:32  ghillie
# nearly complete new script, supports answering machine, fax receiving and remote inquiry now
#
# Revision 1.17  2002/11/29 16:28:43  ghillie
# - updated syntax (connect_telephony -> connect_voice)
#
# Revision 1.16  2002/11/29 11:09:04  ghillie
# renamed CapiCom to CapiSuite (name conflict with MS crypto API :-( )
#
# Revision 1.15  2002/11/25 11:43:43  ghillie
# updated to new syntax
#
# Revision 1.14  2002/11/23 16:16:17  ghillie
# moved switch2fax after audio_receive()
#
# Revision 1.13  2002/11/22 15:48:58  ghillie
# renamed pcallcontrol module to capicom
#
# Revision 1.12  2002/11/22 15:02:39  ghillie
# - added automatic switch between speech and fax
# - some comments added
#
# Revision 1.11  2002/11/19 15:57:18  ghillie
# - Added missing throw() declarations
# - phew. Added error handling. All exceptions are caught now.
#
# Revision 1.10  2002/11/18 12:32:36  ghillie
# - callIncoming lives now in __main__, not necessarily in pcallcontrol any more
# - added some comments and header
#

File: /usr/lib/capisuite/idle_adv.py

#               idle_adv.py - default script for capisuite
#              ---------------------------------------------
#    copyright            : (C) 2007 neobiker
#
#    copyright            : (C) 2002 by Gernot Hillier
#    email                : gernot@hillier.de
#    version              : $Revision: 1.8.2.2 $
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#

import os,re,time,pwd,fcntl
# capisuite stuff
import capisuite,cs_helpers

def idle(capi):
        config=cs_helpers.readConfig()
        spool=cs_helpers.getOption(config,"","spool_dir")
        if (spool==None):
                capisuite.error("global option spool_dir not found.")
                return

        done=os.path.join(spool,"done")+"/"
        failed=os.path.join(spool,"failed")+"/"

        if (not os.access(done,os.W_OK) or not os.access(failed,os.W_OK)):
                capisuite.error("Can't read/write to the necessary spool dirs")
                return

        userlist=config.sections()
        userlist.remove('GLOBAL')

        for user in userlist: # search in all user-specified sendq's
                # skip none user sections
                if not config.has_option(user,'voice_numbers'):
                        break
                userdata=pwd.getpwnam(user)
                outgoing_nr=cs_helpers.getOption(config,user,"outgoing_MSN","")
                if (outgoing_nr==""):
                        incoming_nrs=cs_helpers.getOption(config,user,"fax_numbers","")
                        if (incoming_nrs==""):
                                continue
                        else:
                                outgoing_nr=(incoming_nrs.split(','))[0]

                udir=cs_helpers.getOption(config,"","fax_user_dir")
                if (udir==None):
                        capisuite.error("global option fax_user_dir not found.")
                        return
                udir=os.path.join(udir,user)+"/"
                sendq=os.path.join(udir,"sendq")+"/"
                if (not os.access(udir,os.F_OK)):
                        os.mkdir(udir,0700)
                        os.chown(udir,userdata[2],userdata[3])
                if (not os.access(sendq,os.F_OK)):
                        os.mkdir(sendq,0700)
                        os.chown(sendq,userdata[2],userdata[3])

                files=os.listdir(sendq)
                files=filter (lambda s: re.match("fax-.*\.txt",s),files)

                for job in files:
                        job_fax=job[:-3]+"sff"
                        real_user_c=os.stat(sendq+job).st_uid
                        real_user_j=os.stat(sendq+job_fax).st_uid
                        if (real_user_j!=pwd.getpwnam(user)[2] or real_user_c!=pwd.getpwnam(user)[2]):
                                capisuite.error("job "+sendq+job_fax+" seems to be manipulated (wrong uid)! Ignoring...")
                                continue

                        lockfile=open(sendq+job[:-3]+"lock","w")
                        # read directory contents
                        fcntl.lockf(lockfile,fcntl.LOCK_EX) # lock so that it isn't deleted while sending

                        if (not os.access(sendq+job,os.W_OK)): # perhaps it was cancelled?
                                fcntl.lockf(lockfile,fcntl.LOCK_UN)
                                lockfile.close()
                                os.unlink(sendq+job[:-3]+"lock")
                                continue

                        control=cs_helpers.readConfig(sendq+job)
                        # set DST value to -1 (unknown), as strptime sets it wrong for some reason
                        starttime=(time.strptime(control.get("GLOBAL","starttime")))[0:8]+(-1,)
                        starttime=time.mktime(starttime)
                        if (starttime>time.time()):
                                fcntl.lockf(lockfile,fcntl.LOCK_UN)
                                lockfile.close()
                                os.unlink(sendq+job[:-3]+"lock")
                                continue

                        tries=control.getint("GLOBAL","tries")
                        dialstring=control.get("GLOBAL","dialstring")
                        addressee=cs_helpers.getOption(control,"GLOBAL","addressee","")
                        subject=cs_helpers.getOption(control,"GLOBAL","subject","")
                        mailaddress=cs_helpers.getOption(config,user,"fax_email","")
                        if (mailaddress==""):
                                mailaddress=user
                        fromaddress=cs_helpers.getOption(config,user,"fax_email_from","")
                        if (fromaddress==""):
                                fromaddress=user

                        capisuite.log("job "+job_fax+" from "+user+" to "+dialstring+" initiated",1)
                        result,resultB3 = sendfax(capi,sendq+job_fax,outgoing_nr,dialstring,user,config)
                        tries+=1
                        capisuite.log("job "+job_fax+": result was %x,%x" % (result,resultB3),1)

                        if (result in (0,0x3400,0x3480,0x3490,0x349f) and resultB3==0):
                                movejob(job_fax,sendq,done,user)
                                capisuite.log("job "+job_fax+": finished successfully",1)
                                mailtext="Your fax job to "+addressee+" ("+dialstring+") was sent successfully.\n\n" \
                                  +"Subject: "+subject+"\nFilename: "+job_fax \
                                  +"\nNeeded tries: "+str(tries) \
                                  +("\nLast result: 0x%x/0x%x" % (result,resultB3)) \
                                  +"\n\nIt was moved to file://"+done+user+"-"+job_fax
                                cs_helpers.sendSimpleMail(fromaddress,mailaddress,
                                  "Fax to "+addressee+" ("+dialstring+") sent successfully.",
                                  mailtext)
                        else:
                                max_tries=int(cs_helpers.getOption(config,"","send_tries","10"))
                                delays=cs_helpers.getOption(config,"","send_delays","60,60,60,300,300,3600,3600,18000,36000").split(",")
                                delays=map(int,delays)
                                if ((tries-1)<len(delays)):
                                        next_delay=delays[tries-1]
                                else:
                                        next_delay=delays[-1]
                                starttime=time.time()+next_delay
                                capisuite.log("job "+job_fax+": delayed for "+str(next_delay)+" seconds",2)
                                cs_helpers.writeDescription(sendq+job_fax,"dialstring=\""+dialstring+"\"\n"
                                  +"starttime=\""+time.ctime(starttime)+"\"\ntries=\""+str(tries)+"\"\n"
                                  +"user=\""+user+"\"\naddressee=\""+addressee+"\"\nsubject=\""+subject+"\"\n")
                                if (tries>=max_tries):
                                        movejob(job_fax,sendq,failed,user)
                                        capisuite.log("job "+job_fax+": failed finally",1)
                                        mailtext="I'm sorry, but your fax job to "+addressee+" ("+dialstring \
                                          +") failed finally.\n\nSubject: "+subject \
                                          +"\nFilename: "+job_fax+"\nTries: "+str(tries) \
                                          +"\nLast result: 0x%x/0x%x" % (result,resultB3) \
                                          +"\n\nIt was moved to file://"+failed+user+"-"+job_fax
                                        cs_helpers.sendSimpleMail(fromaddress,mailaddress,
                                          "Fax to "+addressee+" ("+dialstring+") FAILED.",
                                          mailtext)

                        fcntl.lockf(lockfile,fcntl.LOCK_UN)
                        lockfile.close()
                        os.unlink(sendq+job[:-3]+"lock")

def sendfax(capi,job,outgoing_nr,dialstring,user,config):
        try:
                controller=int(cs_helpers.getOption(config,"","send_controller","1"))
                timeout=int(cs_helpers.getOption(config,user,"outgoing_timeout","60"))
                stationID=cs_helpers.getOption(config,user,"fax_stationID")
                if (stationID==None):
                        capisuite.error("Warning: fax_stationID for user "+user+" not set")
                        stationID=""
                headline=cs_helpers.getOption(config,user,"fax_headline","")
                (call,result)=capisuite.call_faxG3(capi,controller,outgoing_nr,dialstring,timeout,stationID,headline)
                if (result!=0):
                        return(result,0)
                capisuite.fax_send(call,job)
                return(capisuite.disconnect(call))
        except capisuite.CallGoneError:
                return(capisuite.disconnect(call))

def movejob(job,olddir,newdir,user):
        os.rename(olddir+job,newdir+user+"-"+job)
        os.rename(olddir+job[:-3]+"txt",newdir+user+"-"+job[:-3]+"txt")

#
# History:
#
# $Log: idle.py,v $
# Revision 1.8.2.2  2004/01/10 07:56:27  gernot
# - fax_numbers is really allowed to miss now (taken from MAIN, 1.11)...
#
# Revision 1.8.2.1  2003/09/21 12:35:20  gernot
# - add 0x349f to list of normal results
#
# Revision 1.8  2003/06/26 11:53:17  gernot
# - fax jobs can be given an addressee and a subject now (resolves #18, reported
#   by Achim Bohnet)
#
# Revision 1.7  2003/06/19 14:58:43  gernot
# - fax_numbers is now really optional (bug #23)
# - tries counter was wrongly reported (bug #29)
#
# Revision 1.6  2003/04/06 11:07:40  gernot
# - fix for 1-hour-delayed sending of fax (DST problem)
#
# Revision 1.5  2003/03/20 09:12:42  gernot
# - error checking for reading of configuration improved, many options got
#   optional, others produce senseful error messages now if not found,
#   fixes bug# 531, thx to Dieter Pelzel for reporting
#
# Revision 1.4  2003/03/13 11:09:58  gernot
# - use stricted permissions for saved files and created userdirs. Fixes
#   bug #544
#
# Revision 1.3  2003/03/09 11:48:10  gernot
# - removed wrong unlock operation (lock not acquired at this moment!)
#
# Revision 1.2  2003/03/06 09:59:11  gernot
# - added "file://" as prefix to filenames in sent mails, thx to
#   Achim Bohnet for this suggestion
#
# Revision 1.1.1.1  2003/02/19 08:19:54  gernot
# initial checkin of 0.4
#
# Revision 1.12  2003/02/18 09:54:22  ghillie
# - added missing lockfile deletions, corrected locking protocol
#   -> fixes Bugzilla 23731
#
# Revision 1.11  2003/02/17 16:48:43  ghillie
# - do locking, so that jobs can be deleted
#
# Revision 1.10  2003/02/10 14:50:52  ghillie
# - revert logic of outgoing_MSN: it's overriding the first number of
#   fax_numbers now
#
# Revision 1.9  2003/02/05 15:59:11  ghillie
# - search for *.txt instead of *.sff so no *.sff which is currently created
#   by capisuitefax will be found!
#
# Revision 1.8  2003/01/31 11:22:00  ghillie
# - use different sendq's for each user (in his user_dir).
# - use prefix user- for names in done and failed
#
# Revision 1.7  2003/01/27 21:56:46  ghillie
# - mailaddress may be not set, that's the same as ""
# - use first entry of fax_numbers as outgoing MSN if it exists
#
# Revision 1.6  2003/01/27 19:24:29  ghillie
# - updated to use new configuration files for fax & answering machine
#
# Revision 1.5  2003/01/19 12:03:27  ghillie
# - use capisuite log functions instead of stdout/stderr
#
# Revision 1.4  2003/01/17 15:09:26  ghillie
# - updated to use new configuration file capisuite-script.conf
#
# Revision 1.3  2003/01/13 16:12:00  ghillie
# - renamed from idle.pyin to idle.py as all previously processed variables
#   stay in the config file and cs_helpers.pyin now
#
# Revision 1.2  2002/12/16 13:07:22  ghillie
# - finished queue processing
#
# Revision 1.1  2002/12/14 13:53:19  ghillie
# - idle.py and incoming.py are now auto-created from *.pyin
#