Как стать автором
Обновить

Asterisk отправка факсов на электронную почту

О чем речь


На просторах интернета можно найти много возможных упоминаний о том, как самостоятельно водрузить asterisk, и поднять на нем сервер приема факсов, однако законченного решения я не нашел.

Проблемы


Спешу заметить что все очень и очень непросто. Если кто-то думает мол «Это же asterisk, это же open-source! Он все и так должен все уметь!», то он сильно ошибается. Open-Source на то и открытый… ну в общем я столкнулся с рядом проблем. Проблемы и их решения под катом...

Проблема 1:

Все что я нашел (и что сам попытался сделать в первую очередь), это патч для asterisk, и сразу передумал. Пересобирать боевой asterisk ради этого, а может это школьная поделка, в общем честно говоря «стремно».

Решение:

Так как я счастливый пользователь Gentoo, в ebuild'е уже был use-флаг span, это некий spandsp. В общем простого:
# echo ">=net-misc/asterisk-1.6.0 span" >> /etc/portage/package.use && emerge asterisk -av
вполне достаточно.

Проблема 2:

Все поставил изменил диалплан, поставил на номер 0 вызов контекста vfax, итак часть extensions.conf:

[sip_users]
exten => 0,1,Gosub(vfax,s,start(${CALLERID(num)})

[vfax]
exten => s,1(start),Set(PREFIX=/home/fax)
exten => s,n,Set(DATE=${STRFTIME(${EPOCH},,%Y-%m-%d)})
exten => s,n,Set(TIME=${STRFTIME(${EPOCH},,%H-%M-%S)})
exten => s,n,NoOp(>>>>>>>>>>>> ${ARG2} <<<<<<<<<<<<<<<)
exten => s,n,Set(DIRECTORY=${PREFIX}/${ARG2}/${DATE}_${TIME})
exten => s,n,System(mkdir -p ${DIRECTORY})
exten => s,n,Set(FILENAME=${DIRECTORY}/fax.tiff)
exten => s,n,Answer()
exten => s,n,ReceiveFAX(${FILENAME})
exten => s,n,System(/opt/bin/fax2mail.py ${FILENAME})
exten => s,n,HangUp()

Все работает все вроде бы прекрасно, да вот тут самое досадное.
Итак забудем о приведенном выше примере, так как в сумме он бесполезен, почему, да очень просто.
дело в том, что если канал рвется с вызывающей стороны, то asterisk перестает выполнять дальнейшие команды диалплана, то есть ту самую заветную /opt/bin/fax2mail.py ${FILENAME} он не выполнит.

Решение:

Если кто меня осуждает за такое решение то пожалуйста, предложите лучше, так сказать займемся опенсорсом.
Я решил что раз пошла такая пьянка, то проверю ка я валидный ли полученный файл, да валидный полный все в порядке, то есть можно написать скрипт который будет ходить по дереву каталогов /home/fax и всем все отправлять, и запускать это хозяйство кроном.

Проблема 3:

Обычно в организации где есть потребность в чем нибудь подобном работает не один человек, поэтому было принято решение отправлять факсы еще и на разные почты, это на самом деле было очень не просто даже осмыслить.

Решение:

Скрипт который все рассылает будет обращаться в базу (Postgresql) и из таблицы выбирать зависимость внутреннего номера и адреса почты.

Что получилось


Итак представляю вашему вниманию не идеальный и не полностью «безбаговый» продукт, в общем случае он работает, остальное проверяйте сами.

Итак а теперь поясню механизм всей работы:

Вариант 1:
1. Поступает входящий звонок и направляется к кому-то
2. Принимающий абонент нажимает #0 (или другую комбинацию для слепого перевода)
3. Сервер ему сообщает о том что он сейчас переведет и запрашивает номер
4. Абонент вновь нажимает 0 и осуществляет слепой перевод на номер факса

Вариант 2:
1. Поступает звонок и направляется на номер 0 (в случае если номер факс-машины выделить отдельно)

Часть extensions.conf:
[users]
;......
exten => 0,1,Gosub(vfax,s,start(${CALLERID(num)},${EXT}))
;......

[vfax]
; Путь к папке с факсами
exten => s,1(start),Set(PREFIX=/home/fax)
exten => s,n,Set(DATE=${STRFTIME(${EPOCH},,%Y-%m-%d)})
exten => s,n,Set(TIME=${STRFTIME(${EPOCH},,%H-%M-%S)})
; XXXXXX внутренний номер секретаря,
; все номера которых нет в базе, или те которые попали на входящую линию
; будут приходить именно на ее почту.

exten => s,n,ExecIf($[${EXISTS(${ARG2})} = 0]?Set(ARG2=XXXXXX))
exten => s,n,NoOp(>>>>>>>>>>>> ${ARG2} <<<<<<<<<<<<<<<)
exten => s,n,Set(DIRECTORY=${PREFIX}/${ARG2}/${DATE}_${TIME})
; создаем папку
exten => s,n,System(mkdir -p ${DIRECTORY})
; и присваиваем переменную ${FILENAME}
exten => s,n,Set(FILENAME=${DIRECTORY}/fax.tiff)
; теперь небольшая хитрость, записываем файл конфигурации факса
; вида
;
; [data]
; filename=${DIRECTORY}/fax.tiff
; date=${DATE}
; time=${TIME}
; from=${ARG1}
; to=${ARG2}

exten => s,n,System(echo "[data]\nfilename=${DIRECTORY}/fax.tiff\ndate=${DATE}\ntime=${TIME}\nfrom=${ARG1}\nto=${ARG2}" >> ${DIRECTORY}/info.conf )
exten => s,n,Answer()
exten => s,n,ReceiveFAX(${FILENAME})
; всякие там факсы "панасоаник" кидают трубку и hangUp чаще всего не выполняется
exten => s,n,HangUp()


Собственно скрипт принимающий отсылающий:
PBX-office ~ # cat /home/fax/fax2mail
#!/usr/bin/python
#-*- coding: utf-8 -*-

from datetime import datetime
from smtplib import SMTP
from os import path,system,chdir,popen
from sys import argv
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.Utils import COMMASPACE, formatdate
from email import Encoders
from psycopg2 import connect as db
from ConfigParser import ConfigParser
from datetime import datetime
from os import path

# Чтение собственного конфига
config = ConfigParser()
config.readfp(open("%s/fax2mail.cfg" % (path.dirname(__file__)),'r'))
config = config._sections

def date2timestamp(*arg):
    """Преобразует подряд идущие аргументы час, мин, сек, год, мес, день в объект datetime"""
    d=dict()
    d['hour'],d['minute'],d['second'],d['year'],d['month'],d['day']=[int(i) for i in arg]
    return datetime(**d)

def sendmail(send_from, send_to, subject, text, user=str(), password=str(), files=[], server='localhost', port=25):
    """ Отсылает письмо, принимает аргументы:
    от кого, кому, Тема, текст, smtp_user, smtp_password, (список путей к прикрепляемым файлам), smtp_сервер, smtp_port """
    assert type(send_to)==list
    assert type(files)==list

    print 'Make mail'
    msg = MIMEMultipart()
    msg.set_charset('UTF-8')
    msg['From'] = send_from
    msg['To'] = "".join(send_to)

    msg['Date'] = formatdate(localtime=True)
    msg['Subject'] = subject

    msg.text=MIMEText(text)
    msg.text.set_charset('UTF-8')
    msg.attach( msg.text )

    print 'Attach Files'
    for f in files:
        part = MIMEBase('application', "octet-stream")
        part.set_payload( open(f,"rb").read() )
        Encoders.encode_base64(part)
        part.add_header('Content-Disposition', 'attachment; filename="%s"' % path.basename(f))
        msg.attach(part)

    print 'make smtp'
    smtp = SMTP()
    smtp.set_debuglevel(False)
    smtp.connect(server,port)
    if not len(user)==0:
        smtp.login(user,password)
    smtp.sendmail(send_from, [send_to], msg.as_string())
    smtp.close()

def fax2mail(filename, dir):
    """Отсылает факс принимает имя конфигфайла и папку"""
    print 'parse config'
    data = ConfigParser()
    data.readfp(open(filename,'r'))
    data = data._sections['data']
    print 'Parse OK', data
    data['timestamp']=date2timestamp(*(data['time'].split("-")+data['date'].split("-")))
    #print diatetime.fromtimestamp(path.getctime(data['filename']))

    print 'Convert'
    density="204x98"
    system("convert -antialias -density %s %s %s 2>/dev/null" % (density, data['filename'], data['filename'].split('.')[0]+'.pdf'))
    print 'Convert OK'
    files=[data['filename'].split('.')[0]+'.pdf',]

    print 'Connect to database',
    try:
        print 'OK'
        connect = db("dbname='%s' user='%s' host='%s' password='%s'" % (config['db']['database'], config['db']['user'], config['db']['host'], config['db']['passwd']))
    except:
        print 'Faled'
        raise NameError('ConnectionFaled')

    print 'Exec query'
    cursor=connect.cursor()
    cursor.execute(	"""	select mail from fax2mail where number='%s' """ % (data['to']))

    address = cursor.fetchone()

    if not isinstance(address, tuple):
        address = (config['mail']['defaultaddress'],)
    cursor.close()
    print 'send to', address
    email={
        'send_from': config['mail']['from'],
        'send_to': list(address),
        'text':"Получен факс %s.\nДля номера: %s\nС номера: %s" % (("%.4d.%.2d.%.2d в %.2d:%.2d:%.2d" % (tuple(data['timestamp'].timetuple())[:6])), data['to'], data['from']),
        'subject': '''Служба автоматического приема факсов компании "Брянские Кабельные Сети"''',
        'files': files,
        'server': config['mail']['server'],
        'user': config['mail']['user'],
        'password': config['mail']['password']
    }

    print 'Send'
    sendmail(**email)
    print 'OK'

    system('rm -r %s' % (dir))

faxs=list()
chdir(config['global']['prefix'])
numbers = [i.replace("/\n","") for i in popen('ls -1d */ 2>/dev/null').readlines()]
for item in numbers:
    chdir(item)
    folders = [config['global']['prefix']+"/"+item+"/"+i.replace("/\n","") for i in popen('ls -1d */ 2>/dev/null').readlines()]
    for k in folders:
        faxs.append(k)
    chdir(config['global']['prefix'])

for fax in faxs:
    try:
        fax2mail("%s/info.conf" % (fax), fax)
    except:
        pass
        #system('rm -r %s' % (fax))

    print fax


И конфиг файл к нему (Думаю он итак понятен):
cat /home/fax/fax2mail.cfg
[global]
prefix=/home/fax

[db]
database=asterisk
user=*****
passwd=*****
host=127.0.0.1

[mail]
defaultaddress=referent@mycompany.com
server=nosslsmtp.org
user=****
password=****
from=fax@mycompany.com


Ну вот и все. Предложения, пожелания, приветствуются. А еще лучше если прям готовые куски кода.
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.