├── Asterisk ├── 11 │ └── 19 │ │ ├── app_queue.c │ │ ├── cdr_pgsql.c │ │ ├── chan_sip.c │ │ ├── queues.conf │ │ ├── reqresp_parser.c │ │ └── sip.h └── README.md ├── CODE_OF_CONDUCT.md ├── IPADDRD ├── BASH │ ├── ipaddr-check │ └── ipaddr-check.conf ├── C │ ├── Makefile │ ├── inet.c │ ├── inet.h │ ├── ipaddrd.conf │ └── ipaddrd.service ├── GO │ ├── SRC │ │ ├── fixme.md │ │ └── main.go │ ├── ipaddr-collector.conf │ └── ipaddr-collector.service └── README.md ├── Instead of CV ├── Part 0.txt ├── Part 1.txt ├── Part 2.txt └── Part 3.txt ├── KAFKA ├── README.md ├── execd │ ├── README.md │ ├── execd.conf │ ├── msg.json │ └── src │ │ └── main.go ├── kafka │ ├── server.properties.template │ └── ssl.hint.sh ├── modules │ ├── report │ │ └── report.go │ ├── xdns │ │ └── xdns.go │ └── xlog │ │ └── xlog.go └── zookeeper │ └── zoo.cfg.template ├── LICENSE ├── PBX ├── README.md ├── ael │ └── func │ │ ├── AON.ael │ │ ├── AlterCID.ael │ │ ├── BlackList.ael │ │ ├── CNAM.ael │ │ ├── ChanSpy.ael │ │ ├── Conference.ael │ │ ├── DND.ael │ │ ├── DTMF.ael │ │ ├── Dial.ael │ │ ├── DutyAgents.ael │ │ ├── Echo.ael │ │ ├── FUNC.INC │ │ ├── Fax.ael │ │ ├── Greeting.ael │ │ ├── Hang.ael │ │ ├── Indication.ael │ │ ├── Intercom.ael │ │ ├── Intro.ael │ │ ├── MOH.ael │ │ ├── MailTo.ael │ │ ├── Mangle.ael │ │ ├── Menu.ael │ │ ├── Milliwatt.ael │ │ ├── MinLoad.ael │ │ ├── Monitor.ael │ │ ├── MoreAnswer.ael │ │ ├── NewCID.ael │ │ ├── Notify.ael │ │ ├── Operator.ael │ │ ├── OrderCall.ael │ │ ├── Pause.ael │ │ ├── PickupGroup.ael │ │ ├── PinCode.ael │ │ ├── PinEvent.ael │ │ ├── PlayFiles.ael │ │ ├── Queue.ael │ │ ├── QueueAgent.ael │ │ ├── Random.ael │ │ ├── RoundRobin.ael │ │ ├── Schedule.ael │ │ ├── SendMessage.ael │ │ ├── SetTransfer.ael │ │ ├── SrcRoute.ael │ │ ├── Survey.ael │ │ ├── Switch.ael │ │ ├── TimeoutRoute.ael │ │ ├── VoiceMail.ael │ │ ├── WorkDays.ael │ │ └── ZTrigger.ael ├── ast │ ├── VersatileLDAP.pm │ ├── autodial.pl │ ├── call.pl │ ├── contents.txt │ ├── convert.sh │ ├── devstate.sh │ ├── duty.pl │ ├── fax.pl │ ├── informer.pl │ ├── schema.py │ ├── schema │ │ ├── __init__.py │ │ └── sip.py │ └── web.py └── sbin │ ├── AntiTheft │ ├── LastSeen │ └── sip-watch │ ├── account │ ├── ast_msg │ ├── autodial │ ├── calls │ ├── card │ ├── cdr │ ├── cdr.rs232 │ ├── cdr.tcp │ ├── conf │ ├── config │ ├── ingress │ ├── ldap2ast │ ├── ldap2nginx │ ├── macro │ ├── pbx_init.sh │ ├── redirect │ ├── route │ ├── sip-log │ ├── sip-peer │ ├── sip-reload │ ├── sip-trace │ ├── sounds │ └── trace ├── README.md ├── RUDDER ├── README.md ├── api.py ├── replicator │ ├── README.md │ ├── etc │ │ ├── apache2 │ │ │ └── rudder_accept.conf │ │ └── replica │ │ │ ├── host │ │ │ └── all.conf │ │ │ └── rule │ │ │ ├── deb.conf │ │ │ ├── ldap.conf │ │ │ └── unb.conf │ ├── inotify.py │ ├── replica │ ├── rule-replace │ └── rule-replica ├── rudder-replica.py └── techniques │ └── fileDistribution │ └── copyHttpFile │ └── 1.0 │ ├── changelog │ ├── copyHttpFile.st │ └── metadata.xml ├── SQL ├── acl.sql ├── cdr.sql └── pbx.sql ├── UPSERT └── upsert ├── VX ├── readme.txt ├── schema └── schema.d │ ├── .modulebuildrc │ ├── action.multipath │ ├── LICENSE.txt │ ├── hooks.pm │ ├── multipath.pm │ ├── multipath_rh.pm │ └── wwid.pm │ ├── action │ ├── LICENSE.txt │ ├── action.pm │ ├── alias.pm │ ├── dial.pm │ ├── ext.pm │ ├── fax.pm │ ├── fnc.pm │ ├── hangup.pm │ ├── hw.pm │ ├── indication.pm │ ├── level.pm │ ├── line.pm │ ├── menu.pm │ ├── playback.pm │ ├── queue.pm │ ├── schedule.pm │ └── vm.pm │ └── hw │ └── spa2102.xml └── x switcher ├── README.md ├── bin └── xswitcher ├── draft.txt ├── src ├── C │ └── x11.c ├── config.go ├── keys.go └── main.go └── xswitcher.conf /Asterisk/11/19/queues.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | ; 3 | ; Global settings for call queues 4 | ; 5 | ; Persistent Members 6 | ; Store each dynamic member in each queue in the astdb so that 7 | ; when asterisk is restarted, each member will be automatically 8 | ; read into their recorded queues. Default is 'yes'. 9 | ; 10 | persistentmembers = yes 11 | ; 12 | ; AutoFill Behavior 13 | ; The old/current behavior of the queue has a serial type behavior 14 | ; in that the queue will make all waiting callers wait in the queue 15 | ; even if there is more than one available member ready to take 16 | ; calls until the head caller is connected with the member they 17 | ; were trying to get to. The next waiting caller in line then 18 | ; becomes the head caller, and they are then connected with the 19 | ; next available member and all available members and waiting callers 20 | ; waits while this happens. The new behavior, enabled by setting 21 | ; autofill=yes makes sure that when the waiting callers are connecting 22 | ; with available members in a parallel fashion until there are 23 | ; no more available members or no more waiting callers. This is 24 | ; probably more along the lines of how a queue should work and 25 | ; in most cases, you will want to enable this behavior. If you 26 | ; do not specify or comment out this option, it will default to no 27 | ; to keep backward compatibility with the old behavior. 28 | ; 29 | autofill = no 30 | ; 31 | ; Monitor Type 32 | ; By setting monitor-type = MixMonitor, when specifying monitor-format 33 | ; to enable recording of queue member conversations, app_queue will 34 | ; now use the new MixMonitor application instead of Monitor so 35 | ; the concept of "joining/mixing" the in/out files now goes away 36 | ; when this is enabled. You can set the default type for all queues 37 | ; here, and then also change monitor-type for individual queues within 38 | ; queue by using the same configuration parameter within a queue 39 | ; configuration block. If you do not specify or comment out this option, 40 | ; it will default to the old 'Monitor' behavior to keep backward 41 | ; compatibility. 42 | ; 43 | monitor-type = MixMonitor 44 | ; 45 | ; UpdateCDR behavior. 46 | ; This option is implemented to mimic chan_agents behavior of populating 47 | ; CDR dstchannel field of a call with an agent name, which you can set 48 | ; at the login time with AddQueueMember membername parameter. 49 | ; 50 | ; updatecdr = no 51 | 52 | ; 53 | ; Note that a timeout to fail out of a queue may be passed as part of 54 | ; an application call from extensions.conf: 55 | ; Queue(queuename,[options],[optionalurl],[announceoverride],[timeout]) 56 | ; example: Queue(dave,t,,,45) 57 | 58 | ; shared_lastcall will make the lastcall and calls received be the same in 59 | ; members logged in more than one queue. 60 | ; This is useful to make the queue respect the wrapuptime of another queue 61 | ; for a shared member 62 | ; 63 | shared_lastcall=no 64 | 65 | announce-frequency=0 66 | announce-holdtime=no 67 | announce-position=no 68 | autopause=no 69 | autopausebusy=no 70 | autopausedelay=0 71 | autopauseunavail=no 72 | eventmemberstatus=yes 73 | eventwhencalled=yes 74 | joinempty=yes 75 | leavewhenempty=no 76 | maxlen=600 ; by KAA 77 | memberdelay=0 78 | penaltymemberslimit=0 79 | periodic-announce-frequency=30 80 | queue-callswaiting=queue-callswaiting 81 | queue-thankyou='' 82 | queue-thereare=queue-thereare 83 | queue-youarenext=queue-youarenext 84 | reportholdtime=no 85 | retry=15 86 | ringinuse=yes 87 | servicelevel=60 88 | strategy=ringall 89 | timeout=0 90 | timeoutpriority=app 91 | timeoutrestart=no 92 | weight=0 93 | wrapuptime=0 94 | 95 | 96 | [Q_Test] 97 | maxlen = 600 98 | autopause = no 99 | retry = 3 100 | wrapuptime = 3 101 | ;musicclass = TEST 102 | timeout = 0 ; 900 103 | weight = 100 104 | ; Announce position & expected time 105 | announce-frequency=60 106 | announce-holdtime=yes 107 | announce-position=more 108 | announce-position-limit=90 109 | 110 | periodic-announce = digits/10,digits/20,digits/30,digits/40 111 | periodic-announce-frequency=2 112 | 113 | ; Log each position change of each queue entry (many events!) 114 | poslog = yes 115 | ; Shift announced position (and calculated time) by this value 116 | offset = 10 117 | ; Minimum on-hold time for further calculations 118 | minhold = 60 119 | ; Supress position increasement announces, when "no". Default "yes". 120 | announce-backoff = no 121 | ; Say no more then X minutes 122 | announce-time-limit = 20 123 | ; Delay seconds before doing first position/time announce 124 | announce-delay = 40 125 | ; Play periodic announces no more then X times, X=-1 to play infinitely 126 | periodic-announce-limit = 4 127 | 128 | 129 | ;member=LOCAL/NonExistent@iax/n 130 | -------------------------------------------------------------------------------- /Asterisk/README.md: -------------------------------------------------------------------------------- 1 | There is placed a number of valuable asterisk patches we done. 2 | 3 | app_queue: 7 new properties are implemented, to accomodate it for wery long call queues. 4 | Added "sayposition" argument to Queue application. 5 | Special thanks to Mark Spencer for the excellent readable code. 6 | My patches are not so clean, sorry. 7 | 8 | chan_sip: "tel:" uri implementation, based on https://issues.asterisk.org/jira/browse/ASTERISK-17179 9 | 10 | cdr_pgsql: dumb patch for dumb code. Hardcoded SQL-string max-size is too low to store call-pass trace in cdr. 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters. 4 | 5 | For more information please visit the [No Code of Conduct](https://github.com/domgetter/NCoC) homepage. 6 | 7 | -------------------------------------------------------------------------------- /IPADDRD/BASH/ipaddr-check.conf: -------------------------------------------------------------------------------- 1 | # ipaddr-check - find IP duplicates in collected reports 2 | # 3 | description "ipaddr-check: find IP duplicates" 4 | 5 | start on (filesystem and net-device-up) 6 | stop on runlevel [06] 7 | 8 | respawn 9 | 10 | env DEFAULTFILE=/etc/default/ipaddr-check 11 | 12 | script 13 | if [ -f "$DEFAULTFILE" ]; then 14 | . "$DEFAULTFILE" 15 | fi 16 | # exec 0>&1 17 | 18 | exec ipaddr-check 19 | end script 20 | -------------------------------------------------------------------------------- /IPADDRD/C/Makefile: -------------------------------------------------------------------------------- 1 | all: ipaddrd 2 | 3 | # https://stackoverflow.com/questions/20051202/how-does-gcc-ld-find-zlib-so >> "-lz" 4 | ipaddrd: inet.c inet.h 5 | $(CC) $< -o $@ -pthread -lz -luuid 6 | #-O0 7 | -------------------------------------------------------------------------------- /IPADDRD/C/inet.h: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE /* To get defns of NI_MAXSERV and NI_MAXHOST */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | // readdir("/run/netns/") 17 | #include 18 | #include 19 | 20 | // http://man7.org/linux/man-pages/man2/setns.2.html 21 | #include 22 | #include 23 | 24 | // Pack ip addresses into udp datagram. 25 | // BITMASK "head" = (0,0,0,0,IFF_UP,IFF_BROADCAST,IFF_PROMISC,is_IPv6) 26 | struct ipv4_frame { 27 | unsigned char head; 28 | uint32_t ip; 29 | } __attribute__((packed)); 30 | 31 | struct ipv6_frame { 32 | unsigned char head; 33 | uint32_t ip[4]; 34 | } __attribute__((packed)); 35 | 36 | // > (200 x IPv6 addresses in 4kB) 37 | #define BUFFER_SIZE (4096) 38 | 39 | struct server_addr { // >> getaddrinfo(const char *node, const char *service, ...) 40 | unsigned char * host; 41 | unsigned char * port; 42 | }; 43 | 44 | struct server_addr server; 45 | 46 | #include 47 | #include 48 | 49 | struct _fp { 50 | unsigned char * frame; 51 | unsigned char * frame_pos; 52 | uint16_t ip_count; 53 | int result; 54 | }; 55 | 56 | // crc32 57 | #include 58 | 59 | // https://stackoverflow.com/questions/17954432/creating-a-daemon-in-linux 60 | #include 61 | #include 62 | #include 63 | #include 64 | 65 | // http://linux.die.net/man/3/uuid_generate 66 | #include 67 | 68 | // Global mutex 69 | #include 70 | int fd_lock; 71 | 72 | // "volatile" prevents "optimize out from code" for variable checks 73 | volatile sig_atomic_t terminated = 0; // Terminate execution on signal 74 | 75 | // CLI 76 | int debug = 0; 77 | unsigned long delay_report = 15; 78 | unsigned long delay_start = 25; 79 | unsigned long delay_random = 1; // All delays are in seconds 80 | // can this #define facilitate further sizeof()? 81 | #define PID_FILE "/var/run/ipaddrd.pid" 82 | unsigned char * pid_file = NULL; 83 | -------------------------------------------------------------------------------- /IPADDRD/C/ipaddrd.conf: -------------------------------------------------------------------------------- 1 | # ipaddrd - Report host ip addresses via UDP, for early dup detection. 2 | # 3 | description "ipaddrd - Report host ip addresses via UDP, for early dup detection." 4 | 5 | start on (filesystem and net-device-up) 6 | stop on runlevel [06] 7 | 8 | # https://raw.githubusercontent.com/ion1/workaround-upstart-snafu/master/workaround-upstart-snafu 9 | expect daemon 10 | 11 | respawn 12 | respawn limit 10 5 13 | 14 | umask 177 15 | env DEFAULTFILE=/etc/default/ipaddrd 16 | 17 | script 18 | HOST=controller:3333 19 | DELAY=25 20 | INTERVAL=15 21 | RANDOM=1 22 | 23 | if [ -f "$DEFAULTFILE" ]; then 24 | . "$DEFAULTFILE" 25 | fi 26 | # exec 0>&1 27 | 28 | exec /usr/local/sbin/ipaddrd -T ${DELAY} -t ${INTERVAL} -r ${RANDOM} -p /run/ipaddrd.pid ${HOST} 29 | end script 30 | -------------------------------------------------------------------------------- /IPADDRD/C/ipaddrd.service: -------------------------------------------------------------------------------- 1 | # /lib/systemd/system/ipaddrd.service 2 | [Unit] 3 | Description=ipaddrd - Report host ip addresses via UDP, for early dup detection. 4 | 5 | [Service] 6 | Type=forking 7 | #PIDFile=/run/ipaddrd.pid # 8 | 9 | Environment="HOST=controller:3333" "DELAY=25" "INTERVAL=15" "RANDOM=1" 10 | EnvironmentFile=-/etc/default/ipaddrd 11 | 12 | ExecStart=/usr/local/sbin/ipaddrd -T ${DELAY} -t ${INTERVAL} -r ${RANDOM} -p /run/ipaddrd.pid ${HOST} 13 | Restart=always 14 | RestartSec=7s 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /IPADDRD/GO/SRC/fixme.md: -------------------------------------------------------------------------------- 1 | http://opennet.ru/openforum/vsluhforumID3/119898.html?n=PnD#189 2 | ** Use something like translate.google.com to translate from russian. 3 | The following advises are plain enough to understand. 4 | 5 | > 1. Вы каждый раз выделаете огромный буфер: 6 | > buf := make([]byte, BUF_LEN) 7 | 8 | Мне самому глаза мозолит, но тут по большому счёту референсный кейс: "по-быстрому обслужи пришедшее соединение и форкни то что долгое в отдельный тред". Соответственно, просто переиспользовать в такой схеме не прокатит. Но таки да, make(buf) всё портит. Не сильно заморачиваясь, есть вариант выделить "кольцо" статикой… 9 | * Альтернативно кон. автомат и пул воркеров как в схемах с nginx (и гонять туда данные по каналам? не, лучше сообщения "возьми этот буфер"), но тут это "из пушки по воробьям". 10 | 11 | > Если убрать `go` от `serve`, то можно было бы один раз выделить 12 | > этот буфер (просто вне `for`-а) и постоянно его использовать. Если `go` 13 | > нужен. то можно постараться либо изменить serve (перенести функционал), чтобы ему 14 | > не нужен был этот буфер, либо в крайнем случае хотя бы 15 | > просто применить `sync.Pool` для переиспользования буферов. По факту в отдельной рутине 16 | > (`go serve`) достаточно было оставить только ту часть, что с syscall-ами, 17 | > ибо вычислительная часть занимает сильно меньше, что позволило бы сделать один 18 | > статический буфер (и избежать даже `sync.Pool`). 19 | > Есть ощущение, что именно эта строчка и портит вам всю статистику. 20 | 21 | Насчёт syscall's — архи-разумная мысль. Обязательно попробую в след. раз. разнести мухи/котлеты. 22 | sync.Pool + "кольцо" буферов — ага. Тогда вообще есть вариант сразу выпустить воркеров "статикой", по одному на буфер. Дальше "семафорить" или блокировкой, или каналы "сделай"/"сделал". 23 | 24 | > 2. Что такое `daemon`? Не вижу его в import-ах. 25 | 26 | Пакет go-daemon → "daemon". 27 | 28 | >[оверквотинг удален] 29 | > report := fmt.Sprintf("addr=%s\nhost=%s\n", addr.String(), hostname) 30 | > if overhead { 31 | > report += "overhead\n" 32 | > } 33 | > ... и т.п. 34 | > Это всё тоже в некоторых случаях нехило давит и на CPU и 35 | > на GC. Опять же, sync.Pool + strings.Builder (вместо Sprintf). 36 | > Кроме того, преобразование []byte в string делает копию (давит и на CPU 37 | > и на GC). Вы могли вообще без string тут обойтись (см. 38 | > bytes.Builder, вместо strings.Builder). 39 | 40 | Да. Не слишком красиво/привычно (аналогичные "Sprintf()" есть в большинстве ЯП ), зато без дурных копий. 41 | 42 | >[оверквотинг удален] 43 | > программу, то с вашей стороны я бы попросил написать unit/integration тест 44 | > для той части кода, что мы хотим оптимизировать. А я со 45 | > своей стороны пообещаю добиться производительности близкой к Сишной без особых костылей. 46 | > Сделать unit/integration тест очень несложно: просто превратите listen_udp в функцию, 47 | > которая принимает этот `pc` извне аргументом типа net.Conn и напишите тест, 48 | > который вызывает эту функцию, посылает туда хотя бы парочку разных пакетов 49 | > и проверяет правильность результата. 50 | > Я добавлю benchmark тест. Сделаю `go test ./ -bench=. -cpuprofile /tmp/cpu.pprof -memprofile 51 | > /tmp/mem.pprof`, глянем на реально слабые места этой программы и исправим ;) 52 | > https://golang.org/pkg/testing/ 53 | 54 | Да, идею я понял: для бенча нужна точка куда удобно напихать данных. 55 | Соответственно, "хороший тон" делать так "из коробки". Конкретно здесь — вынести начало обработки пакета в новую функцию (? +пенальти на вызов. Или тесты в GO умеют изображать работу net-сокетов?). 56 | Быстро не обещаю т.к. конкретно этот код "сделал-работает-забыл". Или "на досуге потренироваться", или подвернётся что-нибудь более требовательное к качеству. 57 | -------------------------------------------------------------------------------- /IPADDRD/GO/ipaddr-collector.conf: -------------------------------------------------------------------------------- 1 | # ipaddr-collector - Collects host ip addresses via UDP, for early dup detection. 2 | # 3 | description "ipaddr-collector - Collects host ip addresses via UDP, for early dup detection." 4 | 5 | start on (filesystem and net-device-up) 6 | stop on runlevel [06] 7 | 8 | # https://raw.githubusercontent.com/ion1/workaround-upstart-snafu/master/workaround-upstart-snafu 9 | # Sorry, upstart knows nothing about golang & [5 x clone()] 10 | #expect fork 11 | #respawn 12 | #respawn limit 10 5 13 | 14 | umask 177 15 | env DEFAULTFILE=/etc/default/ipaddr-collector 16 | 17 | pre-start script 18 | if [ -f "$DEFAULTFILE" ]; then 19 | . "$DEFAULTFILE" 20 | fi 21 | # exec 0>&1 22 | exec /usr/local/sbin/ipaddr-collector 23 | end script 24 | 25 | post-stop script 26 | ps `cat /run/ipaddr-collector.pid` | grep -q "ipaddr-collector" && pkill -F /run/ipaddr-collector.pid 27 | end script 28 | -------------------------------------------------------------------------------- /IPADDRD/GO/ipaddr-collector.service: -------------------------------------------------------------------------------- 1 | # /lib/systemd/system/ipaddr-collector.service 2 | [Unit] 3 | Description=ipaddr-collector - Collects host ip addresses via UDP, for early dup detection. 4 | 5 | [Service] 6 | Type=forking 7 | #PIDFile=/run/ipaddr-collector.pid # 8 | 9 | Environment="" 10 | EnvironmentFile=-/etc/default/ipaddr-collector 11 | 12 | ExecStart=/usr/local/sbin/ipaddr-collector 13 | Restart=always 14 | RestartSec=7s 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /IPADDRD/README.md: -------------------------------------------------------------------------------- 1 | Huh, telemetry is the global trend. 2 | But this service stands aside from privacy draining. It's about early IP addresses duplicates detection. 3 | 4 | I know no one "silver bullet" working on all topologies. This one implies the intranet with m.b. thousands hosts across the number of vlan's. 5 | There can appear duplicates. E.g. due to split-brain in CARP, or defective IP management (some parts of openStack) and so on. 6 | Therefore, while I'm responsible for such a network, I have no correct view outside of hosts. 7 | 8 | But, I can install on each _(in fact, "most of")_ host the micro-tool. 9 | Just to send (time-to-time) the actual list of IPv4|IPv6 toward some collector. 10 | * This tool must be really **micro**. So, C is the ceiling (while asm is better…). 11 | _Under glibc, it consumes from 200k to 2M RSS (depending on linux settings/glibc bugs), for nothing._ 12 | * But collector (server side) could be any (golang is enough). 13 | * The processing can at all be the shell script. 14 | -------------------------------------------------------------------------------- /Instead of CV/Part 0.txt: -------------------------------------------------------------------------------- 1 | *Instead of CV. Part 0* 2 | Introduction. What for this all? 3 | 4 | In this year I'd (once again) noticed that all my "curriculum vitaes" (CV's) are appreciably outdated. 5 | 6 | It's, in fact, ok. Because I'm the good engineer, and most times "job finds me". 7 | 8 | But, what to do with old CV's? It also looks like the task that must be solved. 9 | 10 | The problem I view in CV's for technical vacations: most of them are about skills, therefore being the a set of scalars. HR know almost nothing about such a skills, but can parse them like some signatures. 11 | 12 | This way of recruiting is highly ineffective, and leads to a lot of unnecessary interviews. Because scalar skills are not about "how he/she thinks/solves tasks". Just "this person know something about this word". 13 | 14 | Ok, I understand the problem. What to do with it? 15 | 16 | Now, I'm attempting to build a set of articles/essays. About "what I did/am doing". How I do. For what I do. 17 | 18 | I believe, they can be interesting/useful for my colleagues in engineering. 19 | 20 | And me too. Like learning Chinese/Turkish (in my practice), doing uncommon things improves "mind deviations". Thus, more resources are provided for solving regular tasks. 21 | 22 | Also, I believe that doing so takes a chance to improve interaction. While elaborating sophisticated back-ends is highly interesting, it leaves too few resources to make already done (and working) solutions popular. 23 | 24 | Let's go! 25 | 26 | https://www.linkedin.com/pulse/instead-cv-part-0-dmitry-svyatogorov 27 | -------------------------------------------------------------------------------- /Instead of CV/Part 2.txt: -------------------------------------------------------------------------------- 1 | *Part 2. Attempting to improve the world.* 2 | *Flowcharts markup language, together with it's modular translator.* https://github.com/ds-voix/VX-PBX/tree/master/VX 3 | As it's described above, about all of my R&D can be treated as "backend". In fact, I avoid to build frontends, because it requires very different skills. (And is poorly paid as well… Thus, leading to problems in open-source desktops). 4 | Ok, but setting up PBX by means of UPSERT objects (as described in previous part) was still too complex for interns. 5 | As for Russia, most of intern engineers already know flowcharts. The idea was: 6 | — Someone can explain what to do with the phone call. How to process it, from initiation to termination. 7 | — The specialist can draw this explanation as flowchart. 8 | — The PBX internals are already flowchart-driven. 9 | ¡ Just do an intermediate layer! And make it human readable. (Because I can afford. There was already an example in old network simulator, but it was too weak for my goals.) 10 | 11 | I believe that this project is my best so far. The translator is modular enough, so no restrictions on what to do. 12 | The syntax (I was named it "VX" as "Very eXtendable") is applicable in the same places where flowcharts are. 13 | Right now, it works (together with completely distinct module sets) in: 14 | — PBX: translation to UPSERT objects, also templating for telephone devices. 15 | — SAN: connecting LUN's to hosts (VM's on hypervisors) now don't requires high skills. Thus, my storage network has grown from a few dozen to many hundreds of objects over the past three years. 16 | — PCI-DSS compliant software deployment pipeline. As the core processes can be considered to be flowchart, I was interconnected the schema by means of VX. And the "zero" cycle took only 1 month from paper drafts to working prototype (testing zone, validating zone, production site). Because most of the time was spent on building the conveyor modules, while interconnection was already obvious. 17 | 18 | As for syntax, it is still based on INI/CFG style. Because, on the one hand, it is obvious for humans. And on the other hand, the trivial task is to translate cross json, xml, yaml, toml and other similar grammatics. That is, machine-side presentation can be any. 19 | 20 | *Few details* 21 | The only one keyword in grammar: "Action" (internal grammar is case-insensitive, but managed objects can be case sensitive, or not). The "Action" represents an arrow in the flowchart, while object definitions are in square brackets. All other words are reading into object parameters. At the end of object, the function with its name is called. If one is defined in linked modules. Yes, the very simple way to do*. 22 | 23 | * Of course, there can be forks, combs and so on in schemas. Therefore, the full grammar is in fact a bit more complicated than the description above. 24 | -------------------------------------------------------------------------------- /Instead of CV/Part 3.txt: -------------------------------------------------------------------------------- 1 | *Part 3. From PBX to virtualization and data center management. Transition period.* 2 | Being a highly qualified engineer, I am at the same time a weak businessman. M.b. because good businessman have to lie time-to-time. "Destroy to win." Engineering tasks eliminate the lie, otherwise constructs became broken. 3 | Overall economics in Russia stays in stagnation since (at least) 2011, as it can be seen e.g. from oil consumption monitoring. As a result, most of telecoms are in stagnation as well. Engage in complex engineering is out-of-trend. My remaining telecom projects still carries money in 2019, but too few. 4 | The obvious question was "what the next"? Where is the money still located, and also where are enough engineering tasks? 5 | 6 | When a startup is small, it usually needs cheaper resources. HR also. But what to do when it grows (e.g. due to well-chosen direction)? 7 | At this point of life, the project is becoming extremely unstable (and, in most cases, dies in few years). Inexperienced specialists just has no view about large systems behavior. Lack in skills. 8 | But this point is also the best entry point for high-skilled engineer. (Of course, such a problematic project must still have enough durability. Otherwise no time to deal with it.) 9 | 10 | My first attempt was (as usual) a bit unsuccessful. The project was already under destruction by top management, so I returned to dealing with telecoms, till the second chance. 11 | * Of course, at that time I also did some development. My employer had some deviations, which had led to abnormal projects: 12 | — PBX spamming robot (sorry, it still works). Up to 20M calls per week. 13 | — Very long (asterisk-based) call queues. Up to 720 pending calls (full load for 24xE1). Worse than in Soviet Union shops in 1980-x /in couple with contemporary Crete airport/. 14 | Yes, technically interesting. Like nuclear bomb. 15 | 16 | Asterisk out-of-the-box queues was a bit unsuitable for this manner of implementation, so I made a number of improvements in code. More flexible music-on-hold in couple with a mechanism to allow caller to obtain his last place after hangup/reconnect. https://github.com/ds-voix/VX-PBX/tree/master/Asterisk 17 | * Q: Why asterisk only? A: Because it was made by engineers for engineers. So, it's highly convenient to deal with. I also have been constructed freeswitch-based interfaces etc., but only for what Asterisk cannot do at all. Therefore, I suppose this solutions are too special and "out-of-the-road". 18 | — But also one good deed: VX abstraction was realized at these times, to allow interns (no other staff was available) to deal with PBX. 19 | 20 | …But the second try was successful enough, so I'm still engineering this project. 21 | -------------------------------------------------------------------------------- /KAFKA/README.md: -------------------------------------------------------------------------------- 1 | As of 2019, I daresay Apache kafka is mature enough to deal with. 2 | It's now in fact the only "shared journal" working. With known issues, but, in general, it works. 3 | 4 | On my working area, there was accumulated the set of tasks for such a journal. 5 | Under this folder I will put (my) solutions residing on this bus. 6 | 7 | - "execd" is the most fundamental concept. 8 | The interface between shared messaging bus and any "regular" software. 9 | It although can be used as a kind of "pdsh". "Poll" model, coupled with "push" latency. 10 | The concept is: "Each agent reviews each message on the bus." * But further processing can be filtered. 11 | 12 | - "modules" - common modules between execd, dnsd etc. 13 | 14 | - "kafka" contains the number of solutions to get Kafka server compliant with the project demands: 15 | * High Availability, across the world (thus, 3+ sites) 16 | * SSL: All communications must be encrypted. Connections must be verified against CA. 17 | * ACL: Though access control is evidently inconvenient in current Kafka, it have to be set accurately. 18 | Because any shared communications are although the obvious security hole. 19 | Stay out of hype, tune and control each security relation. Regards. 20 | 21 | - "zookeeper" contains the template for the shared storage config. 22 | As for ZooKeeper v3.4, it has nothing about security. SSL starts with 3.5. 23 | In any case, I recommend to put all about messaging inside some security perimeter. 24 | IMO, no one in java world thinks about security. Because of it's "enterprise" nature. Security brings down KPI. 25 | -------------------------------------------------------------------------------- /KAFKA/execd/README.md: -------------------------------------------------------------------------------- 1 | - src/main.go: execd source was done monolithic. Because I think it's "all about one". 2 | - execd.conf: The sample config. Default locations is "/etc/execd/execd.conf" 3 | - msg.json: Example on how to compose the message. Push ("produce") it into journal ("topic"): 4 | kafkacat -P -b ext-kafka.xxx.local:9093 -X "security.protocol=ssl" -X "ssl.ca.location=/etc/ssl/certs/" -X topic.request.required.acks=all -t knot.test msg.json 5 | 6 | -------------------------------------------------------------------------------- /KAFKA/execd/execd.conf: -------------------------------------------------------------------------------- 1 | # Currently, no need in. Kafka is enough. 2 | #[ZooKeeper] # Bindings to ZooKeeper cluster 3 | # Cluster = ["ext-kafka.mhd.local","ext-kafka.ggs.local","ext-kafka.knk.local"] 4 | # Timeout = 3 5 | # xxx = "" # With embedded TOML parser, unknown keys are silently ignored 6 | 7 | [Kafka] 8 | # Brokers must be SSL only. It's fundamental restriction for execd. 9 | # Because all consumed messages are completelly trusted. 10 | Brokers = ["ext-kafka.xxx.local:9093", "ext-kafka.yyy.local:9093", "ext-kafka.zzz.local:9093"] 11 | # CA certificate can be distributed with execd, in case it's inconvinient to import one into host's root CA's. 12 | caCertFile = "ca.pem" 13 | # Client private key, together with CA-signed certificate. 14 | # !!! Go don't have function to decrypt PKCS8 keys in standard library. !!! 15 | # openssl default is PKCS8 pbeWITHMD5ndDES-CBC, while https://godoc.org/github.com/youmark/pkcs8 has AES-256-CBC only 16 | # make(key&CSR): openssl req -x509 -new -passout pass:"samplePwd" -subj "/CN=linux-i0x0.mara.local" -days 36500 -outform PEM -out linux-i0x0.mara.local.csr -sha512 -newkey rsa:2048 -keyout key.pem 17 | # test: openssl rsa -noout -text -in key.pem 18 | # 19 | #openssl req -x509 -new -nodes -sha512 -newkey rsa:2048 -keyout key.pem -subj "/CN=linux-i0x0.mara.local" -days 36500 -out /dev/null 20 | #openssl req -new -key key.pem -out linux-i0x0.mara.local.csr -subj "/CN=linux-i0x0.mara.local" 21 | #>>> openssl ca -config sign.config -in linux-i0x0.mara.local.csr -out linux-i0x0.mara.local.crt -batch 22 | 23 | # privateKeyFile = "key.pem" 24 | # privateKeyPassword = "XXXsamplePwd" # M.b. encoded. Not supported yet. 25 | # certFile = "linux-i0x0.mara.local.crt" 26 | 27 | # !!! In production, trusted root MUST be imported into the local storage !!! 28 | # InsecureSkipVerify = true 29 | # https://github.com/Shopify/sarama/blob/master/config.go 30 | ClientID = "" # defaults to host fqdn 31 | DialTimeout = 10 # How long to wait for the initial connection. 32 | ReadTimeout = 10 # How long to wait for a response. 33 | WriteTimeout = 10 # How long to wait for a transmit. 34 | KeepAlive = 30 35 | # LocalAddr = "0.0.0.0" # Speak from this IP. 36 | 37 | # kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 1 --topic "knot.test.xxx" 38 | # kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 1 --topic "knot.test.yyy" 39 | # kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 1 --topic "knot.test.zzz" 40 | [Consume] # Must be RO for all consumers to avoid loops whith misconfig!!! 41 | # OffsetNewest int64 = -1 42 | # OffsetOldest int64 = -2 43 | Topics = ["knot.test", "knot.test.xxx"] # Bad idea to use multiple topics, it leads to problems in further debug! 44 | 45 | # Partition = 1 46 | # Where to save offsets. M.b. the good idea to store together with other state. To simplify backup/restore 47 | LocalDirectory = "/var/tmp/" 48 | 49 | FetchMax = 1048576 # 1MB is large enough. M.b. up to 256 MB messages due to hardcoded ChannelBufferSize! 50 | RetryBackoff = 10 # One retry per 10 seconds 51 | RetryMax = 8640 # !!! 10^4 takes about 1MB of RSS, because sarama fills some linear structure to do !!! 52 | 53 | # Defer = 90 # Defer execution of received commands (seconds after command.timestamp) 54 | 55 | [Produce] 56 | Topic = "knot.log" # Single log for all agents 57 | Partition = 1 58 | # Where to save reports. Leave empty if there's no need in. 59 | LocalDirectory = "/var/tmp/reports" 60 | 61 | MaxMessageBytes = 16777216 62 | Timeout = 30 63 | RetryBackoff = 10 64 | RetryMax = 8640 # !!! 10^4 takes about 1MB of RSS, because sarama fills some linear structure to do !!! 65 | Respawn = 29 66 | 67 | [Hooks] 68 | PreStart = "/etc/execd/hooks/pre-start" 69 | # Exec "Hooks.Start" just after catching up actual topics positions 70 | # Therefore, the payload can be started after obtaining of actual state. 71 | Start = "/etc/execd/hooks/start" # systemctl disable knot.service 72 | # Exec "Hooks.Stop" during the regular shutdown 73 | Stop = "/etc/execd/hooks/stop" 74 | 75 | # Store report to "Produce.Topic", like ordinary messages does. 76 | # Produce = true 77 | Tag = "Hooks" 78 | 79 | [Workers] 80 | Required = ["/usr/sbin/knotd"] # Start inside self context. Panic if dies. 81 | # Respawn = [] # Start inside self context. Respawn if dies. 82 | Test = "/usr/sbin/knotc conf-abort" # Execute after each message processing. Terminate self-operation if exits non-zero. 83 | TestDelay = 10 # Wait up to 10 seconds till "Required" bacames available for "Test" at Init stage. 84 | TestAttempts = 10 # Perform N attempts during the delay. Continue execution at first success. 85 | HookStop = "/etc/execd/hooks/stop" # ??? 86 | -------------------------------------------------------------------------------- /KAFKA/execd/msg.json: -------------------------------------------------------------------------------- 1 | {"producer": "ext-kafka.xxx.local", 2 | "uuid": "96b35ec3-388e-4d1a-be21-c28b680ab9a1", 3 | "host_regex": "", 4 | "error_fails": false, 5 | "no_exec": false, 6 | "run_as": "root:root", 7 | "use_shell": true, 8 | "set_env": ["var1=value for var1", "var2=\n\"y\""], 9 | "set_dir": "/root", 10 | "no_wait": false, 11 | "timeout": 3, 12 | "max_reply": 128, 13 | "commands": [ 14 | { 15 | "id": 1, 16 | "no_exec": false, 17 | "run_as": "root:root", 18 | "use_shell": true, 19 | "set_env": ["var1=value for var1", "var2=\n\"y\""], 20 | "set_dir": "/root", 21 | "no_wait": false, 22 | "timeout": 3, 23 | "max_reply": 128, 24 | "command": "cat .bash_history >&2", 25 | "args": ["-v"] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /KAFKA/kafka/ssl.hint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # *** 3 | umask 077 4 | # https://docs.confluent.io/current/security/security_tutorial.html#generating-keys-certs # 5 | 6 | mkdir -p /etc/kafka/ssl/ 7 | cd /etc/kafka/ssl/ 8 | 9 | ### Root of the trust ### 10 | # CA key pair 11 | openssl req -x509 -new -keyout CA.key -days 36500 -out CA.crt -subj "/CN=ext-kafka" -passout pass:*** 12 | # -import: Make truststore, add CA 13 | keytool -keystore kafka.server.truststore.jks -deststoretype pkcs12 -alias kafka-CA -import -file CA.crt -storepass *** -noprompt 14 | 15 | ### !!! "CA.crt" must be trusted by clients, otherwise "verify error:num=19:self signed certificate in certificate chain" 16 | 17 | ### One key pair per host ### 18 | # -genkey: Make keystore, add cert 19 | # !!! "-keyalg RSA", DSA is unsupported outside of java!!! 20 | keytool -keystore kafka.server.xxx.jks -deststoretype pkcs12 -alias ext-kafka.xxx -validity 3650 -genkeypair -keyalg RSA -keypass *** -storepass *** -noprompt -dname "CN=ext-kafka.xxx.local" 21 | keytool -keystore kafka.server.yyy.jks -deststoretype pkcs12 -alias ext-kafka.yyy -validity 3650 -genkeypair -keyalg RSA -keypass *** -storepass *** -noprompt -dname "CN=ext-kafka.yyy.local" 22 | keytool -keystore kafka.server.zzz.jks -deststoretype pkcs12 -alias ext-kafka.zzz -validity 3650 -genkeypair -keyalg RSA -keypass *** -storepass *** -noprompt -dname "CN=ext-kafka.zzz.local" 23 | # !!! Warning: Different store and key passwords not supported for PKCS12 KeyStores. Ignoring user-specified -keypass value. 24 | 25 | # -certreq: CSRs 26 | keytool -keystore kafka.server.xxx.jks -alias ext-kafka.xxx -certreq -file ext-kafka.xxx.csr -storepass *** -noprompt 27 | keytool -keystore kafka.server.yyy.jks -alias ext-kafka.yyy -certreq -file ext-kafka.yyy.csr -storepass *** -noprompt 28 | keytool -keystore kafka.server.zzz.jks -alias ext-kafka.zzz -certreq -file ext-kafka.zzz.csr -storepass *** -noprompt 29 | # Signed CRTs 30 | openssl x509 -req -CA CA.crt -CAkey CA.key -in ext-kafka.xxx.csr -out ext-kafka.xxx.crt -days 3650 -CAcreateserial -passin pass:*** 31 | openssl x509 -req -CA CA.crt -CAkey CA.key -in ext-kafka.yyy.csr -out ext-kafka.yyy.crt -days 3650 -CAcreateserial -passin pass:*** 32 | openssl x509 -req -CA CA.crt -CAkey CA.key -in ext-kafka.zzz.csr -out ext-kafka.zzz.crt -days 3650 -CAcreateserial -passin pass:*** 33 | 34 | # Import root CA into keystore 35 | keytool -keystore kafka.server.xxx.jks -alias kafka-CA -import -file CA.crt -storepass *** -noprompt 36 | keytool -keystore kafka.server.yyy.jks -alias kafka-CA -import -file CA.crt -storepass *** -noprompt 37 | keytool -keystore kafka.server.zzz.jks -alias kafka-CA -import -file CA.crt -storepass *** -noprompt 38 | 39 | # Import kafka certs into keystore 40 | keytool -keystore kafka.server.xxx.jks -alias ext-kafka.xxx -import -file ext-kafka.xxx.crt -storepass *** -noprompt 41 | keytool -keystore kafka.server.yyy.jks -alias ext-kafka.yyy -import -file ext-kafka.yyy.crt -storepass *** -noprompt 42 | keytool -keystore kafka.server.zzz.jks -alias ext-kafka.zzz -import -file ext-kafka.zzz.crt -storepass *** -noprompt 43 | 44 | 45 | ### Now, copy "kafka.server.XXX.jks" & "kafka.server.truststore.jks" to appropriate hosts (xxx,yyy,zzz) 46 | ### Edit /etc/kafka/server.properties 47 | ### On each host: 48 | chown -R kafka:kafka /etc/kafka/ 49 | # chown root:root /etc/kafka/ssl/CA.key 50 | chmod 400 /etc/kafka/*.properties 51 | chmod 400 /etc/kafka/ssl/* 52 | 53 | systemctl restart kafka.service 54 | # Errors in SSL leads to java exceptions. Which are throwed out (traditionaly for java progers). 55 | less /var/log/kafka/controller.log 56 | less /var/log/kafka/server.log 57 | # [kafka.cluster.Partition] INFO [Partition knot.test-0 broker=1] Expanding ISR from 1,2 to 1,2,3 (kafka.cluster.Partition) 58 | 59 | ### Check SSL after kafka config patch & restart 60 | openssl s_client -connect ext-kafka.xxx.local:9093 61 | 62 | 63 | 64 | 65 | ### ZooKeeper ### 66 | mkdir -p /etc/zookeeper/ssl/ 67 | cp /etc/kafka/ssl/*.jks /etc/zookeeper/ssl/ 68 | chown -R zookeeper:zookeeper /etc/zookeeper/ssl 69 | chmod 400 /etc/zookeeper/ssl/* 70 | 71 | ### config 72 | #### SSL ### https://zookeeper.apache.org/doc/r3.5.5/zookeeperAdmin.html 73 | # !!! ZooKeeper must be upgraded to 3.5.latest! 3.4 has no SSL at all !!! 74 | #sslQuorum=true 75 | #serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory 76 | #ssl.quorum.trustStore.location=/etc/zookeeper/ssl/kafka.server.truststore.jks 77 | #ssl.quorum.trustStore.password=*** 78 | #ssl.quorum.keyStore.location=/etc/zookeeper/ssl/kafka.server.xxx.jks 79 | #ssl.quorum.keyStore.password=*** 80 | -------------------------------------------------------------------------------- /KAFKA/zookeeper/zoo.cfg.template: -------------------------------------------------------------------------------- 1 | # The number of milliseconds of each tick 2 | tickTime=2000 3 | 4 | # The number of ticks that the initial 5 | # synchronization phase can take 6 | initLimit=10 7 | 8 | # The number of ticks that can pass between 9 | # sending a request and getting an acknowledgement 10 | syncLimit=5 11 | 12 | # the directory where the snapshot is stored. 13 | # do not use /tmp for storage, /tmp here is just 14 | # example sakes. 15 | dataDir=/var/lib/zookeeper 16 | 17 | # the port at which the clients will connect 18 | clientPort=2181 19 | 20 | #### SSL ### https://zookeeper.apache.org/doc/r3.5.5/zookeeperAdmin.html 21 | # ZooKeeper must be upgraded to 3.5.latest 22 | #sslQuorum=true 23 | #serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory 24 | #ssl.quorum.trustStore.location=/etc/zookeeper/ssl/kafka.server.truststore.jks 25 | #ssl.quorum.trustStore.password=*** 26 | #ssl.quorum.keyStore.location=/etc/zookeeper/ssl/kafka.server.xxx.jks 27 | #ssl.quorum.keyStore.password=*** 28 | 29 | # the maximum number of client connections. 30 | # increase this if you need to handle more clients 31 | #maxClientCnxns=60 32 | 33 | # 34 | # Be sure to read the maintenance section of the 35 | # administrator guide before turning on autopurge. 36 | # 37 | # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance 38 | # 39 | 40 | # The number of snapshots to retain in dataDir 41 | #autopurge.snapRetainCount=3 42 | 43 | # Purge task interval in hours 44 | # Set to "0" to disable auto purge feature 45 | #autopurge.purgeInterval=1 46 | 47 | # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_configuration 48 | # server.x=[hostname]:nnnnn[:nnnnn] 49 | server.1=ext-kafka.xxx.local:2888:3888 50 | server.2=ext-kafka.yyy.local:2888:3888 51 | server.3=ext-kafka.zzz.local:2888:3888 52 | 53 | # https://svn.apache.org/repos/asf/zookeeper/trunk/docs/zookeeperAdmin.html 54 | # Does nothing, in fact 55 | #admin.enableServer=true 56 | #admin.serverPort=8080 57 | 58 | # http://zookeeper.apache.org/doc/r3.4.13/zookeeperAdmin.html 59 | # !!! 60 | tcpKeepAlive=True 61 | 62 | 4lw.commands.whitelist=* 63 | -------------------------------------------------------------------------------- /PBX/README.md: -------------------------------------------------------------------------------- 1 | ael: Asterisk-side runtime 2 | ast: Number of scripts for asterisk PBX, histotically was placed under /ast/ 3 | sbin: VX PBX toolset, except of UPSERT and SCHEMA 4 | AntiTheft: Simple GeoIP-based SIP account region-locking 5 | -------------------------------------------------------------------------------- /PBX/ael/func/AON.ael: -------------------------------------------------------------------------------- 1 | // USSR "AON" CID request simulation 2 | // insert into "Func" values ('090','ALL',NULL,'AON','9','3','','Indication(86400)'); 3 | macro AON(Wait,Count,NoPlayback,fn) { 4 | NoOp(${leg1}>>${leg2} FUNCTION AON(${Wait},${Count},${NoAnswer},${fn})); 5 | catch h { // without a catch, dialplan stops execution on hangup !!! 6 | hang=1; 7 | return; 8 | } 9 | 10 | Set(CHANNEL(language)=${DEFAULT_LANG}); 11 | RESULT=OK; 12 | 13 | if ("${Wait}"!="") { 14 | Ringing(); 15 | Wait(${Wait}); 16 | } 17 | 18 | // if ("${NoAnswer}"!="1") { 19 | Answer; 20 | // } 21 | 22 | Wait(0.3); 23 | 24 | switch ("${Count}") { 25 | case "1": 26 | PlayTones(0/200,500/140,0/2000); break; 27 | case "2": 28 | PlayTones(0/200,500/140,0/200,500/140,0/2000); break; 29 | case "3": 30 | PlayTones(0/200,500/140,0/200,500/140,0/200,500/140,0/2000); break; 31 | case "4": 32 | PlayTones(0/200,500/140,0/200,500/140,0/200,500/140,0/200,500/140,0/2000); break; 33 | case "5": 34 | PlayTones(0/200,500/140,0/200,500/140,0/200,500/140,0/200,500/140,0/200,500/140,0/2000); break; 35 | default: 36 | Playback(aon); 37 | } 38 | 39 | Wait(2); 40 | StopPlayTones(); 41 | if ("${NoPlayback}"="") { 42 | Playback(connected); 43 | Wait(1); 44 | } 45 | 46 | #include "ael/func/FUNC.INC" 47 | return; 48 | } // macro AON(Wait,Count,NoAnswer,fn) 49 | -------------------------------------------------------------------------------- /PBX/ael/func/BlackList.ael: -------------------------------------------------------------------------------- 1 | // BlackList trap (play some activity imitation with no answer) 2 | // insert into "Func" values ('090','ALL','TrapBL','greeting/Priv1&greeting/Priv2'); 3 | macro TrapBL(Play,MOH,Time) { 4 | NoOp(${leg1}>>${leg2} FUNCTION TrapBL(${Play},${MOH},${Time})); 5 | catch h { // without a catch, dialplan stops execution on hangup !!! 6 | hang=1; 7 | return; 8 | } 9 | Progress(); 10 | Set(CHANNEL(language)=${DEFAULT_LANG}); 11 | if ("${Play}"!="") Playback(${Play},noanswer); 12 | MusicOnHold(${MOH},${Time}); 13 | return; 14 | } // TrapBL(Play,MOH,Time) 15 | 16 | 17 | // Switch route basing on BlackList 18 | // insert into "Func" values ('090','ALL','DialBL','581','583:584:585','^58','1'); 19 | macro DialBL(Route,RouteBL,RegExp,Threshold) { 20 | NoOp(${leg1}>>${leg2} FUNCTION DialBL(${Route},${RouteBL},${RegExp},${Threshold})); 21 | catch h { // without a catch, dialplan stops execution on hangup !!! 22 | hang=1; 23 | return; 24 | } 25 | 26 | Set(RESULT=Invalid Route); 27 | if ("${Route}"="") return; 28 | if ("${RouteBL}"="") Set(LOCAL(RouteBL)=${Route}); 29 | Set(LOCAL(timeout)=${CUT(Threshold,:,2)}); 30 | if ("${timeout}"="") Set(LOCAL(timeout)=1 day); 31 | Set(LOCAL(Threshold)=${CUT(Threshold,:,1)}); 32 | if ("${Threshold}"="") Set(LOCAL(Threshold)=1); 33 | 34 | 35 | if (${ODBC_BL(${leg1},${BIND},${timeout},${RegExp})}>${Threshold}) { 36 | Set(LOCAL(Route)=${RouteBL}); 37 | } 38 | 39 | RESULT=OK; 40 | 41 | for (LOCAL(i)=1; ${i}<=${FIELDQTY(Route,:)}; LOCAL(i)=${i}+1) { 42 | Set(LOCAL(rt)=${rt}&LOCAL/${CUT(Route,:,${i})}@iax/n); 43 | } 44 | 45 | // Set(__NO_TRANSFER=${NoTransfer}); 46 | Set(CHANNEL(language)=${DEFAULT_LANG}); 47 | Dial(${rt:1},,fg); // Spawn parallel calls 48 | 49 | if ("${DIALSTATUS}"="ANSWER") RESULT=OK; else Set(RESULT=${DIALSTATUS}); 50 | return; 51 | } // DialBL(Route,RouteBL,RegExp,Threshold) 52 | 53 | 54 | // The same as DialBL, except of ${Route} specifies full one 55 | // insert into "Func" values ('090','ALL','RouteBL','581@iax','583@iax:584@iax','^58','1'); 56 | macro RouteBL(Route,RouteBL,RegExp,Threshold) { 57 | NoOp(${leg1}>>${leg2} FUNCTION DialBL(${Route},${RouteBL},${RegExp},${Threshold})); 58 | catch h { // without a catch, dialplan stops execution on hangup !!! 59 | hang=1; 60 | return; 61 | } 62 | 63 | Set(RESULT=Invalid Route); 64 | if ("${Route}"="") return; 65 | if ("${RouteBL}"="") Set(LOCAL(RouteBL)=${Route}); 66 | Set(LOCAL(timeout)=${CUT(Threshold,:,2)}); 67 | if ("${timeout}"="") Set(LOCAL(timeout)=1 day); 68 | Set(LOCAL(Threshold)=${CUT(Threshold,:,1)}); 69 | if ("${Threshold}"="") Set(LOCAL(Threshold)=1); 70 | 71 | 72 | if (${ODBC_BL(${leg1},${BIND},${timeout},${RegExp})}>${Threshold}) { 73 | Set(LOCAL(Route)=${RouteBL}); 74 | } 75 | 76 | RESULT=OK; 77 | 78 | for (LOCAL(i)=1; ${i}<=${FIELDQTY(Route,:)}; LOCAL(i)=${i}+1) { 79 | Set(LOCAL(rt)=${rt}&LOCAL/${CUT(Route,:,${i})}/n); 80 | } 81 | 82 | Set(__NO_TRANSFER=${NoTransfer}); 83 | Set(CHANNEL(language)=${DEFAULT_LANG}); 84 | Dial(${rt:1},,fg); // Spawn parallel calls 85 | 86 | if ("${DIALSTATUS}"="ANSWER") RESULT=OK; else Set(RESULT=${DIALSTATUS}); 87 | return; 88 | } // RouteBL(Route,RouteBL,RegExp,Threshold) 89 | 90 | 91 | // Switch func basing on BlackList 92 | // insert into "Func" values ('090','ALL',NULL,'BL','_Dial(LOCAL/28583@iax/n|300|TtKkfg):_Dial(LOCAL/28584@iax/n|300|TtKkfg)','_Dial(LOCAL/28531@iax/n|300|TtKkfg)','^58','1:7 day'); 93 | macro BL(Func,FuncBL,RegExp,Threshold) { 94 | NoOp(${leg1}>>${leg2} FUNCTION BL(${Func},${FuncBL},${RegExp},${Threshold})); 95 | catch h { // without a catch, dialplan stops execution on hangup !!! 96 | hang=1; 97 | return; 98 | } 99 | 100 | Set(RESULT=Invalid Func); 101 | if ("${Func}"="") return; 102 | if ("${FuncBL}"="") Set(LOCAL(FuncBL)=${Func}); 103 | 104 | Set(LOCAL(timeout)=${CUT(Threshold,:,2)}); 105 | if ("${timeout}"="") Set(LOCAL(timeout)=1 day); 106 | Set(LOCAL(Threshold)=${CUT(Threshold,:,1)}); 107 | if ("${Threshold}"="") Set(LOCAL(Threshold)=1); 108 | 109 | 110 | NoOp(ODBC_BL(${leg1},${BIND},${timeout},${RegExp}) > ${Threshold}); 111 | if (${ODBC_BL(${leg1},${BIND},${timeout},${RegExp})}>${Threshold}) { 112 | Set(LOCAL(Func)=${FuncBL}); 113 | Set(DATA=${DATA},FUNC=${CONTEXT},RE=${RegExp},MATCH=1); 114 | } else Set(DATA=${DATA},FUNC=${CONTEXT};RE=${RegExp};MATCH=0); 115 | 116 | Set(CDR(x-data)={${DATA:1}}); 117 | 118 | RESULT=OK; 119 | 120 | for (LOCAL(i)=1; ${i} <= ${FIELDQTY(Func,:)}; LOCAL(i)=${i}+1) { 121 | Set(LOCAL(fn)=${CUT(Func,:,${i})}); 122 | 123 | #include "ael/func/FUNC.INC" 124 | if ("${RESULT}"="OK"|"${RESULT}"="NOANSWER") break; 125 | } 126 | 127 | if (("${RESULT}"!="OK") & ("${FallBack}"!="")) { 128 | Set(LOCAL(fn)=${FallBack}); 129 | #include "ael/func/FUNC.INC" 130 | } 131 | return; 132 | } // BL(Func,FuncBL,RegExp,Threshold) 133 | -------------------------------------------------------------------------------- /PBX/ael/func/CNAM.ael: -------------------------------------------------------------------------------- 1 | // Request CallerID(Name) from HTTP service. Use %CID%=cid, %DID%=dnid in URL. 2 | // insert into "Func" values ('090','ALL',NULL,'CNAM','http://a3.xxx.ru/CNAM?phone=%CID%','Func(090.1)'); 3 | macro CNAM(URL,fn,Var) { 4 | NoOp(${leg1}>>${leg2} FUNCTION CNAM(${URL},${fn},${Var})); 5 | catch h { // without a catch, dialplan stops execution on hangup !!! 6 | hang=1; 7 | return; 8 | } 9 | Set(CURLOPT(httptimeout)=1); 10 | 11 | Set(LOCAL(URL)=${STRREPLACE(URL,%CID%,${cid})}); 12 | Set(LOCAL(URL)=${STRREPLACE(URL,%DID%,${dnid})}); 13 | 14 | Set(CALLERID(name)=${CURL(${URL})}); 15 | 16 | Set(DATA=${DATA},FUNC=${CONTEXT}); 17 | Set(CDR(x-data)={${DATA:1}}); 18 | 19 | Set(RESULT=Invalid Func); 20 | if ("${fn}"="") return; 21 | 22 | RESULT=OK; 23 | 24 | for (LOCAL(i)=1; ${i}<=${FIELDQTY(Var,\,)}; LOCAL(i)=${i}+1) { 25 | Set(LOCAL(v)=${CUT(Var,\,,${i})}); 26 | Set(${CUT(v,=,1)}=${CUT(v,=,2)}); 27 | } 28 | 29 | #include "ael/func/FUNC.INC" 30 | 31 | if ("${hang}"="1") Hangup(); 32 | return; 33 | } // macro CNAM(URL,fn,Var) 34 | -------------------------------------------------------------------------------- /PBX/ael/func/ChanSpy.ael: -------------------------------------------------------------------------------- 1 | // Connect to the already bridged call. 2 | macro SpyExten(Exten,fn) { 3 | catch h { // without a catch, dialplan stops execution on hangup !!! 4 | hang=1; 5 | return; 6 | } 7 | Set(DATA=${DATA},FUNC=${CONTEXT}); 8 | Set(CDR(x-data)={${DATA:1}}); 9 | 10 | // ChanSpy(SIP/${BIND},Be(${Exten})Eq); 11 | ChanSpy(SIP/${BIND}+${Exten},ESse(SIP/${BIND}+${Exten})); // qe(${Exten}@exten) 12 | 13 | if ("${fn}"="") return; 14 | #include "ael/func/FUNC.INC" 15 | return; 16 | } // macro DTMF(MaxDigits,Prefix,Timeout,fn) 17 | 18 | 19 | //* Права можно задавать через ${SPYGROUP} 20 | -------------------------------------------------------------------------------- /PBX/ael/func/Conference.ael: -------------------------------------------------------------------------------- 1 | // Conferencing via ConfBridge 2 | macro Conf(Name,Options,Template) { 3 | NoOp(${leg1}>>${leg2} FUNCTION Conf(${Name},${Options},${Template})); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | hang=1; 6 | return; 7 | } 8 | Set(DATA=${DATA},FUNC=${CONTEXT};CONF=${Name}); 9 | Set(CDR(x-data)={${DATA:1}}); 10 | 11 | if ("${Name}"="") Set(LOCAL(Name)=CONF-${dnid}); 12 | if ("${Options}"="") local Options=M; 13 | if ("${Template}"="") local Template=default_user; 14 | 15 | Set(CONFBRIDGE(user,template)=${Template}); 16 | Set(CONFBRIDGE(user,music_on_hold_when_empty)=yes); 17 | Set(CONFBRIDGE(user,announce_only_user)=no); 18 | 19 | ConfBridge(${Name}); 20 | return; 21 | } // macro Conf(Name,Options,Template) 22 | 23 | macro conf(CONFNO) { 24 | // Pass channel to dynamically created conference ${CONFNO} 25 | catch h { // without a catch, dialplan stops execution on hangup !!! 26 | hang=1; 27 | return; 28 | } 29 | Set(DATA=${DATA},FUNC=${CONTEXT};CONF=${CONFNO}); 30 | Set(CDR(x-data)={${DATA:1}}); 31 | 32 | MeetMe(${CONFNO},dM1); 33 | return; 34 | } // macro conf(CONFNO) 35 | -------------------------------------------------------------------------------- /PBX/ael/func/DND.ael: -------------------------------------------------------------------------------- 1 | // Switches "DND" mode on Line On|Off 2 | // insert into "Func" values ('090','ALL',NULL,'SwitchDND','453','1'); 3 | macro SwitchDND(Line,SetState) { 4 | NoOp(${leg1}>>${leg2} FUNCTION SwitchDND(${Line},${SetState})); 5 | catch h { // without a catch, dialplan stops execution on hangup !!! 6 | hang=1; 7 | return; 8 | } 9 | if ("${Line}"="") Set(LOCAL(Line)=${cid}); 10 | 11 | Set(DATA=${DATA},FUNC=${CONTEXT};LINE=${Line}); 12 | Set(CDR(x-data)={${DATA:1}}); 13 | 14 | ClearHash(ext); 15 | Set(HASH(ext)=${ODBC_EXTEN(${Line},${BIND})}); 16 | 17 | if ("${HASH(ext,DND)}"!="1"&"${SetState}"!="0"|"${SetState}"="1") { 18 | Set(ODBC_EXTEN(${HASH(ext,Exten)},${HASH(ext,BIND)},DND)=1); 19 | ClearHash(ext); 20 | Set(HASH(ext)=${ODBC_EXTEN(${Line})},${BIND}); 21 | if ("${BIND}"!="") Set(LOCAL(Line)=${BIND}+${Line}); 22 | DEVICE_STATE(Custom:${Line})=BUSY; 23 | PauseQueueMember(,LOCAL/${Line}@iax/n,,SwitchDND); 24 | PauseQueueMember(,LOCAL/!${Line}@iax/n,,SwitchDND); 25 | &Event(dnd-on); 26 | // Playback(ivr/transfer-on); 27 | } else { 28 | Set(ODBC_EXTEN(${HASH(ext,Exten)},${HASH(ext,BIND)},DND)=0); 29 | ClearHash(ext); 30 | Set(HASH(ext)=${ODBC_EXTEN(${Line})},${BIND}); 31 | if ("${BIND}"!="") Set(LOCAL(Line)=${BIND}+${Line}); 32 | UnpauseQueueMember(,LOCAL/${Line}@iax/n,,SwitchDND); 33 | UnpauseQueueMember(,LOCAL/!${Line}@iax/n,,SwitchDND); 34 | &Event(dnd-off); 35 | // Playback(ivr/transfer-off); 36 | } 37 | return; 38 | } // macro SwitchDND(Line,SetState) 39 | -------------------------------------------------------------------------------- /PBX/ael/func/DTMF.ael: -------------------------------------------------------------------------------- 1 | // Process DTMF input 2 | macro DTMF(MaxDigits,Prefix,Timeout,fn) { 3 | catch h { // without a catch, dialplan stops execution on hangup !!! 4 | hang=1; 5 | return; 6 | } 7 | Set(DATA=${DATA},FUNC=${CONTEXT}); 8 | Set(CDR(x-data)={${DATA:1}}); 9 | 10 | num=; 11 | Read(num,dial,${MaxDigits},i,,${Timeout}); 12 | if ("${num}"!="") { 13 | Set(LOCAL(fn)=${STRREPLACE(fn,%DTMF,${num})}); 14 | } 15 | 16 | #include "ael/func/FUNC.INC" 17 | return; 18 | } // macro DTMF(MaxDigits,Prefix,Timeout,fn) 19 | -------------------------------------------------------------------------------- /PBX/ael/func/Dial.ael: -------------------------------------------------------------------------------- 1 | // Asterisk's Dial() envelope, able to to serial calls until "ANSWER" result 2 | // Returns "OK" when Dial() succeeds, or explanation string in other case 3 | // insert into "Func" values ('090','ALL','Dial','LOCAL/28583@iax/n:LOCAL/28585@iax/n','300','TtKkfg'); 4 | 5 | macro _Dial(Spec,Timeout,Options,URL) { 6 | NoOp(${leg1}>>${leg2} FUNCTION _Dial(${Spec},${Timeout},${Options},${URL})); 7 | catch h { // without a catch, dialplan stops execution on hangup !!! 8 | hang=1; 9 | return; 10 | } 11 | Set(DATA=${DATA},FUNC=${CONTEXT};Spec=${Spec}); 12 | Set(CDR(x-data)={${DATA:1}}); 13 | 14 | Set(RESULT=Invalid Spec); 15 | NoOp(Spec = ${Spec}); 16 | if ("${Spec}"="") return; 17 | 18 | if ("${Timeout}"="") Timeout=300; 19 | if ("${Options}"="") Options=fg; 20 | 21 | RESULT=OK; 22 | 23 | Set(LOCAL(qty)=${FIELDQTY(Spec,:)}); 24 | // local qty=${qty}+1; 25 | 26 | Set(CHANNEL(language)=${DEFAULT_LANG}); 27 | 28 | for (LOCAL(i)=1; ${i}<=${qty}; LOCAL(i)=${i}+1) { 29 | Set(LOCAL(spc)=${CUT(Spec,:,${i})}); 30 | if ("${spc:-1:1}"="!") { 31 | Set(LOCAL(spc)=${spc:0:-1}${leg2}); 32 | } 33 | Dial(${spc},${Timeout},${Options},${URL}); 34 | Set(RESULT=${DIALSTATUS}); 35 | if ("${DIALSTATUS}"="ANSWER") break; 36 | } 37 | 38 | // if ("${DIALSTATUS}"!="ANSWER") Dial(LOCAL/${FallBack}@iax/n,300,TtKkfg); 39 | if ("${DIALSTATUS}"="ANSWER") RESULT=OK; else Set(RESULT=${DIALSTATUS}); 40 | return; 41 | } // macro _Dial(Spec,Timeout,Options,URL) 42 | -------------------------------------------------------------------------------- /PBX/ael/func/DutyAgents.ael: -------------------------------------------------------------------------------- 1 | // Spawn calls to the duty agents gang, 2 | // according to it's settings 3 | // insert into "Func" values ('090','ALL',NULL,'DialDuty','1'); 4 | macro DialDuty(Duty,FallBack,NoTransfer) { 5 | NoOp(${leg1}>>${leg2} FUNCTION DialDuty(${Duty},${FallBack},${NoTransfer})); 6 | catch h { // without a catch, dialplan stops execution on hangup !!! 7 | hang=1; 8 | return; 9 | } 10 | Set(DATA=${DATA},FUNC=${CONTEXT};DUTY=${Duty}); 11 | Set(CDR(x-data)={${DATA:1}}); 12 | 13 | Set(RESULT=Invalid Duty); 14 | if ("${Duty}"="") return; 15 | 16 | //"Exten","BIND","Amount","Announce" 17 | Set(HASH(dt)=${ODBC_DUTY(${Duty})}); 18 | if ("${HASH(dt,Exten)}"="") return; 19 | 20 | if ("${BIND}"="") Set(__BIND=${HASH(dt,BIND)}); 21 | 22 | Set(CDR(accountcode)=${HASH(dt,Exten)}); 23 | Set(CDR(x-domain)=${BIND}); 24 | 25 | //"Agent","Description","VALID","Active" 26 | Set(LOCAL(agents)=${ODBC_DUTY_AGENTS(${Duty})}); 27 | 28 | for (LOCAL(i)=1; ${i}<=${HASH(dt,Amount)}; LOCAL(i)=${i}+1) { 29 | Set(HASH(da)=${ODBC_FETCH(${agents})}); 30 | if ("${ODBC_FETCH_STATUS}"="SUCCESS") { 31 | if ("${HASH(dt,VALID)}"!=0|"${n}"="") Set(LOCAL(n)=${n}&LOCAL/${HASH(dt,Exten)}-${HASH(da,Agent)}@out/n); 32 | } else break; 33 | } 34 | 35 | Set(__NO_TRANSFER=${NoTransfer}); 36 | Set(CHANNEL(language)=${DEFAULT_LANG}); 37 | 38 | Set(RESULT=No agents); 39 | if ("${n:1}"!="") Dial(${n:1},,crfg); // Spawn parallel calls 40 | 41 | if ("${DIALSTATUS}"!="ANSWER") { 42 | if ("${FallBack}"!="") Dial(LOCAL/${FallBack}@iax/n,,fg); 43 | } 44 | 45 | if ("${DIALSTATUS}"="ANSWER") RESULT=OK; else Set(RESULT=${DIALSTATUS}); 46 | return; 47 | } // DialDuty(Duty,FallBack,NoTransfer) 48 | -------------------------------------------------------------------------------- /PBX/ael/func/Echo.ael: -------------------------------------------------------------------------------- 1 | // ECHO service 2 | macro Echo() { 3 | NoOp(${leg1}>>${leg2} FUNCTION Echo()); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | hang=1; 6 | return; 7 | } 8 | Set(DATA=${DATA},FUNC=${CONTEXT}); 9 | Set(CDR(x-data)={${DATA:1}}); 10 | 11 | Set(CHANNEL(language)=${DEFAULT_LANG}); 12 | Set(LOCAL(file)=/var/spool/asterisk/monitor/echo-${cid}-${EPOCH}); 13 | 14 | Answer; 15 | Record(${file}.alaw,,20,ky); 16 | Playback(beep&${file}); 17 | 18 | System(/bin/rm -f "${file}.alaw"); 19 | 20 | return; 21 | } // macro Echo() 22 | -------------------------------------------------------------------------------- /PBX/ael/func/FUNC.INC: -------------------------------------------------------------------------------- 1 | // 2 | Set(LOCAL(fnc)=${CUT(fn,\(,1)}); 3 | Set(LOCAL(arg)=${CUT(fn,\(,2-)}); 4 | local args=${FIELDQTY(arg,\x29)}-1; 5 | //NoOp(AAA=${FIELDQTY(arg,\x29)}=${CUT(arg,\),1-${args})}= =${arg}=); 6 | // Set(LOCAL(arg)=${CUT(arg,\),1)}); 7 | Set(LOCAL(arg)=${CUT(arg,\),1-${args})}); 8 | 9 | 10 | switch ("${fnc}") { 11 | case "Func": 12 | // NoOp(HASH(res)=ODBC_FUNC(${arg},${cid},${BIND})); 13 | Set(HASH(res)=${ODBC_FUNC(${arg},${cid},${BIND})}); // Is function defined? 14 | if ("${HASH(res,Macro)}"!="") { 15 | &${HASH(res,Macro)}(${HASH(res,P1)},${HASH(res,P2)},${HASH(res,P3)},${HASH(res,P4)}); 16 | NoOp(RESULT=${RESULT}); 17 | } 18 | break; 19 | 20 | case "Exten": 21 | Set(HASH(res)=${ODBC_EXTEN(${arg},${BIND})}); 22 | if ("${HASH(res,Exten)}"!="") { 23 | Set(LOCAL(args)=${REPLACE(arg,\|,\,)}); 24 | Set(__dnid=${CUT(args,\,,1)}); 25 | if ("${CUT(args,\,,2)}"!="") Set(__BIND=${CUT(args,\,,2)}); 26 | &exten(fake); 27 | } 28 | break; 29 | 30 | default: 31 | Set(LOCAL(args)=${REPLACE(arg,\|,\,)}); 32 | &${fnc}(${args}); 33 | } 34 | 35 | NoOp(RESULT=${RESULT}); 36 | -------------------------------------------------------------------------------- /PBX/ael/func/Fax.ael: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ds-voix/VX-PBX/75e3292dbc21876405258cb60037853137727a83/PBX/ael/func/Fax.ael -------------------------------------------------------------------------------- /PBX/ael/func/Greeting.ael: -------------------------------------------------------------------------------- 1 | // Answer, play greeting file switching by "var", then route to "r1"|"r2" 2 | macro greeting(play,var,r1,r2) { 3 | NoOp(${leg1}>>${leg2} FUNCTION greeting(${play},${var},${r1},${r2})); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | hang=1; 6 | return; 7 | } 8 | Set(LOCAL(status)=${ODBC_VAR(${var})}); 9 | if ("${status}"="") local status=0; 10 | if ("${r2}"="") Set(LOCAL(r2)=${r1}); 11 | 12 | Answer; 13 | Set(CHANNEL(language)=${DEFAULT_LANG}); 14 | num=; 15 | if ("${play}"!="") Read(LOCAL(num),${play}${status},7,,,3); 16 | if ("${num}"!="") { 17 | Set(LOCAL(num)=${ODBC_ALIAS_I(${leg2},${num})}); 18 | Set(LOCAL(level)=${ODBC_LEVEL(${num})}); 19 | if (${level}<3) { // Local call 20 | Dial(LOCAL/${num}@iax/n,,fg); 21 | return; 22 | } 23 | } 24 | if ("${r1}"="") return; 25 | if (${status}=0) Dial(LOCAL/${r1}/n,,fg); else Dial(LOCAL/${r2}/n,,fg); 26 | return; 27 | } // macro greeting(play,var,r1,r2) 28 | -------------------------------------------------------------------------------- /PBX/ael/func/Hang.ael: -------------------------------------------------------------------------------- 1 | // Answer, play tones,then call fn 2 | // Returns "OK" when Func RESULT=OK, or explanation string in other case 3 | // insert into "Func" values ('090','ALL',NULL,'Indication','FIFO(mark|1234)'); 4 | macro Hang() { 5 | NoOp(${leg1}>>${leg2} FUNCTION Hang()); 6 | catch h { // without a catch, dialplan stops execution on hangup !!! 7 | hang=1; 8 | return; 9 | } 10 | Set(DATA=${DATA},FUNC=${CONTEXT}); 11 | Set(CDR(x-data)={${DATA:1}}); 12 | 13 | h=h; 14 | goto exten|${h}|1; 15 | return; 16 | } // macro Hang() 17 | -------------------------------------------------------------------------------- /PBX/ael/func/Indication.ael: -------------------------------------------------------------------------------- 1 | // Answer, play tones,then call fn 2 | // Returns "OK" when Func RESULT=OK, or explanation string in other case 3 | // insert into "Func" values ('090','ALL',NULL,'Indication','FIFO(mark|1234)'); 4 | macro Indication(fn,Timeout,NoTransfer,Limit) { 5 | NoOp(${leg1}>>${leg2} FUNCTION Indication(${fn},${Timeout},${NoTransfer},${Limit})); 6 | catch h { // without a catch, dialplan stops execution on hangup !!! 7 | hang=1; 8 | return; 9 | } 10 | 11 | Set(DATA=${DATA},FUNC=${CONTEXT};LIMIT=${Limit}); 12 | Set(CDR(x-data)={${DATA:1}}); 13 | 14 | if ("${Limit}"!="") { 15 | Set(GROUP(Q)=${dnid}); 16 | if (${GROUP_COUNT(${dnid}@Q)}>${Limit}) { 17 | Congestion(); 18 | return; 19 | } 20 | } 21 | 22 | Set(RESULT=Invalid Func); 23 | if ("${fn}"="") return; 24 | 25 | Set(CHANNEL(language)=${DEFAULT_LANG}); 26 | Set(__NO_TRANSFER=${NoTransfer}); 27 | 28 | RESULT=OK; 29 | 30 | local pos=1; 31 | 32 | Answer; 33 | Wait(1); 34 | Ringing(); 35 | if ("${Timeout}"="") local Timeout=3; 36 | Wait(${Timeout}); 37 | 38 | #include "ael/func/FUNC.INC" 39 | return; 40 | } // macro Indication(fn,Timeout,NoTransfer,Limit) 41 | 42 | 43 | macro Tones(Tone,Timeout,NoAnswer,fn) { 44 | NoOp(${leg1}>>${leg2} FUNCTION Tones(${Tone},${Timeout},${NoAnswer},${fn})); 45 | catch h { // without a catch, dialplan stops execution on hangup !!! 46 | hang=1; 47 | return; 48 | } 49 | Set(DATA=${DATA},FUNC=${CONTEXT}); 50 | Set(CDR(x-data)={${DATA:1}}); 51 | 52 | Set(RESULT=Invalid Tone); 53 | if ("${Tone}"="") return; 54 | 55 | Set(CHANNEL(language)=${DEFAULT_LANG}); 56 | 57 | RESULT=OK; 58 | 59 | local pos=1; 60 | 61 | if ("${NoAnswer}"!=1) { 62 | Answer; 63 | Wait(1); 64 | } 65 | 66 | if ("${Timeout}"="") local Timeout=3; 67 | 68 | PlayTones(${Tone}); 69 | Wait(${Timeout}); 70 | 71 | if ("${fn}"="") return; 72 | 73 | #include "ael/func/FUNC.INC" 74 | return; 75 | } // macro Tones(Tone,Timeout,NoAnswer,fn) 76 | -------------------------------------------------------------------------------- /PBX/ael/func/Intercom.ael: -------------------------------------------------------------------------------- 1 | // InterCom to console 2 | macro intercom(spec,header) { 3 | NoOp(${leg1}>>${leg2} FUNCTION intercom(${spec},${header})); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | hang=1; 6 | return; 7 | } 8 | 9 | if ("${spec}"="") { 10 | Set(spec=Console/s@console); // SIP/intercom1 11 | } 12 | if ("${header}"!="") { 13 | SIPaddheader(${header}); // SIPaddheader(Alert-Info: Ring Answer); 14 | } 15 | Playback(beep); 16 | // SIPaddheader(Alert-Info: Ring Answer); 17 | Set(CHANNEL(language)=${DEFAULT_LANG}); 18 | Dial(${spec},10,S(30)); 19 | return; 20 | } 21 | 22 | context console { // Audio console like InterCom 23 | s => { 24 | Answer; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /PBX/ael/func/MOH.ael: -------------------------------------------------------------------------------- 1 | // Answer, test MOH 2 | macro PlayMoh(moh,time,NoAnswer) { 3 | NoOp(${leg1}>>${leg2} FUNCTION PlayMoh(${moh},${time},${NoAnswer})); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | hang=1; 6 | return; 7 | } 8 | 9 | Set(DATA=${DATA},FUNC=${CONTEXT};MOH=${moh}); 10 | Set(CDR(x-data)={${DATA:1}}); 11 | 12 | if ("${NoAnswer}"!="1") { 13 | Answer; 14 | } 15 | 16 | Wait(1); 17 | 18 | Set(CHANNEL(language)=${DEFAULT_LANG}); 19 | 20 | MusicOnHold(${moh},${time}); 21 | return; 22 | } // macro PlayMoh(moh,time,NoAnswer) 23 | -------------------------------------------------------------------------------- /PBX/ael/func/MailTo.ael: -------------------------------------------------------------------------------- 1 | // Send email notification. Address=to:from 2 | // Call fn, if one specified 3 | macro MailTo(Address,Subject,Body,fn) { 4 | NoOp(${leg1}>>${leg2} FUNCTION MailTo(${Address},${Subject},${Body},${fn})); 5 | catch h { // without a catch, dialplan stops execution on hangup !!! 6 | hang=1; 7 | return; 8 | } 9 | Set(DATA=${DATA},FUNC=${CONTEXT}); 10 | Set(CDR(x-data)={${DATA:1}}); 11 | 12 | Set(To=${CUT(Address,:,1)}); 13 | Set(From=${CUT(Address,:,2)}); 14 | 15 | Set(RESULT=Invalid To); 16 | if ("${To}"="") return; 17 | 18 | if ("${From}"="") { 19 | Set(LOCAL(From)=pbx@`/bin/hostname -f`); 20 | } 21 | 22 | RESULT=OK; 23 | 24 | 25 | Set(LOCAL(Subject)=${EVAL(${Subject})}); 26 | Set(LOCAL(Body)=${EVAL(${Body})}); 27 | 28 | Set(LOCAL(Subject)=${STRREPLACE(Subject,%CID,${leg1})}); 29 | Set(LOCAL(Subject)=${STRREPLACE(Subject,%DID,${leg2})}); 30 | 31 | Set(LOCAL(Body)=${STRREPLACE(Body,%CID,${leg1})}); 32 | Set(LOCAL(Body)=${STRREPLACE(Body,%DID,${leg2})}); 33 | 34 | System(/usr/local/sbin/mailer -f "${From}" -t "${To}" -s "${Subject}" --charset "UTF-8" -b "${Body}"); 35 | 36 | #include "ael/func/FUNC.INC" 37 | return; 38 | } // macro MailTo(Address,Subject,Body,fn) 39 | -------------------------------------------------------------------------------- /PBX/ael/func/Mangle.ael: -------------------------------------------------------------------------------- 1 | // Mangle CID and DID, following Mangle record 2 | macro Mangle(NRec,fn,SetCID,SetDNID) { 3 | NoOp(${leg1}>>${leg2} FUNCTION Mangle(${NRec},${fn})); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | hang=1; 6 | return; 7 | } 8 | 9 | Set(RESULT=Invalid Func); 10 | if ("${fn}"="") return; 11 | 12 | RESULT=OK; 13 | 14 | Set(HASH(mangle)=${ODBC_MANGLE(${NRec},${dnid},${cid})}); 15 | 16 | Set(__dnid=${HASH(mangle,DNID)}); 17 | Set(__cid=${HASH(mangle,CID)}); 18 | 19 | if("${SetDNID}"="1") Set(CALLERID(dnid)=${dnid}); 20 | if("${SetCID}"="1") Set(CALLERID(all)=${cid}); 21 | if("${SetCID}"="1") Set(CALLERID(ani)=${cid}); 22 | 23 | Set(DATA=${DATA},FUNC=${CONTEXT};MANGLE=${NRec}); 24 | Set(CDR(x-data)={${DATA:1}}); 25 | 26 | #include "ael/func/FUNC.INC" 27 | return; 28 | } // macro Mangle(NRec,fn) 29 | -------------------------------------------------------------------------------- /PBX/ael/func/Menu.ael: -------------------------------------------------------------------------------- 1 | // Dynamic menu for Asterisk 2 | macro Menu(NRec,NoHang,Limit,OnHang) { 3 | NoOp(${leg1}>>${leg2} FUNCTION Menu(${NRec},${NoHang},${Limit},${OnHang})); 4 | // Do menu processing based on "Menu" table 5 | catch h { // whitout a catch, dialplan stops execution on hangup !!! 6 | hang=1; 7 | Set(LOCAL(fn)=${OnHang}); 8 | if ("${fn}"="") return; 9 | #include "ael/func/FUNC.INC" 10 | return; 11 | } 12 | ClearHash(menu); 13 | Set(HASH(menu)=${ODBC_MENU(${NRec})}); 14 | 15 | Set(LOCAL(hello)=${STRREPLACE(HASH(menu,Hello),&,&menu/)}); 16 | Set(LOCAL(prompt)=${STRREPLACE(HASH(menu,Prompt),&,&menu/)}); 17 | 18 | if ("${HASH(menu,NRec)}"="") { // No such menu! 19 | return; 20 | } 21 | 22 | if ("${Limit}"!="") { 23 | Set(LOCAL(RouteGroup)=${CUT(Limit,:,2)}); 24 | if ("${RouteGroup}"="") Set(LOCAL(RouteGroup)=${dnid}); 25 | 26 | Set(LOCAL(Limit)=${CUT(Limit,:,1)}); 27 | 28 | Set(GROUP(R)=${RouteGroup}); 29 | if (${GROUP_COUNT(${RouteGroup}@R)}>${Limit}) { 30 | if ("${FallBack}"="") { 31 | Congestion(); 32 | return; 33 | } // else Set(LOCAL(fn)=${FallBack}); 34 | } 35 | } 36 | 37 | Set(CHANNEL(language)=${DEFAULT_LANG}); 38 | Answer(1000); 39 | local num=; 40 | local loop=0; 41 | while (("${num}"="")&(${loop}<=${HASH(menu,Repeat)})&("${hang}"!="1")) { 42 | if ((${loop}=0)&("${hello}"!="")) { 43 | Read(num,menu/${hello},1,,,1); 44 | if ("${num}"=""&"${READSTATUS}"!="TIMEOUT") Set(num=#); 45 | Set(LOCAL(READSTATUS)=); 46 | } else { 47 | Read(num,menu/${prompt},1,,,${HASH(menu,Timeout)}); 48 | if ("${num}"=""&"${READSTATUS}"!="TIMEOUT") Set(num=#); 49 | } 50 | local loop=${loop}+1; 51 | 52 | if ("${num}"!="") { // Some key pressed [0-9*#] 53 | Set(LOCAL(fn)=${HASH(menu,${num})}); 54 | 55 | Set(DATA=${DATA},FUNC=${CONTEXT};MENU=${NRec};KEY=${num}); 56 | Set(CDR(x-data)={${DATA:1}}); 57 | } else { 58 | Set(LOCAL(fn)=); 59 | } 60 | local num=; 61 | 62 | if ((${loop}>${HASH(menu,Repeat)}&"${fn}"="")|"${READSTATUS}"="TIMEOUT") { 63 | if ("${HASH(menu,TimeoutAction)}"!="") { // Do something for stupid user (on no key pressed) 64 | Set(LOCAL(fn)=${HASH(menu,TimeoutAction)}); 65 | if ("${CUT(fn,:,1)}"!="BACK") { // Call this function 66 | 67 | Set(DATA=${DATA},FUNC=${CONTEXT};MENU=${NRec};KEY=t); 68 | Set(CDR(x-data)={${DATA:1}}); 69 | 70 | #include "ael/func/FUNC.INC" 71 | if ("${NoHang}"!="1"&"${hang}"!="0") hang=1; else hang=; 72 | 73 | Set(HASH(menu)=${ODBC_MENU(${NRec})}); // Because it is rewritten on recursion!!! 74 | 75 | if ("${BACK}"!="") { 76 | Set(LOCAL(fn)=${BACK}); 77 | goto back; 78 | } 79 | } else { // Try return to previous menu 80 | if ("${HASH(menu,Parent)}"!="") { 81 | Set(BACK=${CUT(fn,:,2-)}); 82 | hang=0; 83 | NoOp(Parent=${HASH(menu,Parent)}); 84 | return; 85 | } 86 | } 87 | } // if ("${HASH(menu,TimeoutAction)}"!="") 88 | return; 89 | } 90 | 91 | back: 92 | if ("${fn}"!="") { 93 | if ("${CUT(fn,:,1)}"!="BACK") { // Call this function 94 | #include "ael/func/FUNC.INC" 95 | if ("${NoHang}"!="1"&"${hang}"!="0") hang=1; else hang=; 96 | 97 | Set(HASH(menu)=${ODBC_MENU(${NRec})}); // Because it is rewritten on recursion!!! 98 | 99 | if ("${BACK}"!="") { 100 | Set(LOCAL(fn)=${BACK}); 101 | goto back; 102 | } 103 | } else { // Try return to previous menu 104 | if ("${HASH(menu,Parent)}"!="") { 105 | Set(BACK=${CUT(fn,:,2-)}); 106 | hang=0; 107 | NoOp(Parent=${HASH(menu,Parent)}); 108 | return; 109 | } 110 | } 111 | } // if (${func}) 112 | } // while 113 | 114 | // NoOp(END MENU); 115 | return; 116 | } // macro Menu(NRec,NoHang,Limit) 117 | -------------------------------------------------------------------------------- /PBX/ael/func/Milliwatt.ael: -------------------------------------------------------------------------------- 1 | // Generate 1004Hz 2 | macro 1004Hz() { 3 | NoOp(${leg1}>>${leg2} FUNCTION 1004Hz()); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | hang=1; 6 | return; 7 | } 8 | Set(DATA=${DATA},FUNC=${CONTEXT}); 9 | Set(CDR(x-data)={${DATA:1}}); 10 | 11 | Set(CHANNEL(language)=${DEFAULT_LANG}); 12 | 13 | Answer; 14 | Milliwatt(); 15 | 16 | return; 17 | } // macro 1004Hz() 18 | -------------------------------------------------------------------------------- /PBX/ael/func/MinLoad.ael: -------------------------------------------------------------------------------- 1 | // Tries to dial phone with lowest load from ${Route} 2 | // Try to call ${FallBack} number when no answer was detected 3 | // Returns "OK" when Dial() succeeds, or explanation string in other case 4 | // Pay attention to set REAL EXTENSIONS here, as load must be counted 5 | // insert into "Func" values ('090','ALL','DialMinLoad','28583:28584:28585','531','0'); 6 | 7 | macro DialMinLoad(Route,FallBack,NoTransfer) { 8 | NoOp(${leg1}>>${leg2} FUNCTION DialMinLoad(${Route},${FallBack},${NoTransfer})); 9 | catch h { // without a catch, dialplan stops execution on hangup !!! 10 | hang=1; 11 | return; 12 | } 13 | 14 | Set(RESULT=Invalid Route); 15 | if ("${Route}"="") return; 16 | Set(__NO_TRANSFER=${NoTransfer}); 17 | Set(__IN_QUEUE=1); 18 | 19 | RESULT=OK; 20 | 21 | Set(LOCAL(timeout)=${CUT(RR,:,2)}); 22 | if ("${timeout}"="") Set(LOCAL(timeout)=${ODBC_CONST(timeout-dial)}); 23 | if ("${timeout}"="") Set(LOCAL(timeout)=300); 24 | Set(LOCAL(RR)=${CUT(RR,:,1)}); 25 | 26 | local dialed=; 27 | 28 | if ("${BIND}"!="") { 29 | Set(LOCAL(bnd)=${BIND}+); 30 | } else { 31 | local bnd=; 32 | } 33 | 34 | for (LOCAL(j)=1; ${j}<=${FIELDQTY(Route,:)}; LOCAL(j)=${j}+1) { 35 | local load=9999; 36 | for (LOCAL(i)=1; ${i}<=${FIELDQTY(Route,:)}; LOCAL(i)=${i}+1) { 37 | Set(LOCAL(rt)=${CUT(Route,:,${i})}); 38 | if (${REGEX("${rt}" ${dialed})}=1) continue; 39 | // Check for target extension load 40 | local count=${GROUP_COUNT(${bnd}${rt}@I)}+${GROUP_COUNT(${bnd}${rt}@O)}; 41 | 42 | if (${count}<${load}) { 43 | local load=${count}; 44 | local rtt=${rt}; 45 | if (${load}=0) break; 46 | } 47 | } 48 | 49 | Set(CHANNEL(language)=${DEFAULT_LANG}); 50 | Dial(LOCAL/${rtt}@iax/n,,fg); 51 | Set(LOCAL(dialed)=${dialed} ${rtt}); 52 | if ("${DIALSTATUS}"="ANSWER") break; 53 | } 54 | Set(__IN_QUEUE=); 55 | 56 | if ("${DIALSTATUS}"!="ANSWER") { 57 | if ("${FallBack}"!="") Dial(LOCAL/${FallBack}@iax/n,,fg); 58 | } 59 | 60 | if ("${DIALSTATUS}"="ANSWER") RESULT=OK; else Set(RESULT=${DIALSTATUS}); 61 | return; 62 | } // DialMinLoad(Route,FallBack,NoTransfer) 63 | -------------------------------------------------------------------------------- /PBX/ael/func/Monitor.ael: -------------------------------------------------------------------------------- 1 | // Record dialing peer to mp3, by default on standard monitoring path 2 | macro monitor(file) { 3 | NoOp(${leg1}>>${leg2} FUNCTION monitor(${file})); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | System(/usr/bin/lame -m m --cbr -b 32 "${mfile}.wav" "${mfile}.mp3" && /bin/rm -f "${mfile}.wav"); 6 | hang=1; 7 | return; 8 | } 9 | Set(DATA=${DATA},FUNC=${CONTEXT};FILE=${file}); 10 | Set(CDR(x-data)={${DATA:1}}); 11 | 12 | Set(CHANNEL(language)=${DEFAULT_LANG}); 13 | if ("${file}"="") Set(LOCAL(file)=/var/spool/asterisk/monitor); 14 | 15 | Set(LOCAL(mfile)=${file}/${cid}/${STRFTIME(${EPOCH},,%Y%m%d-%H%M%S)}-monitor); 16 | 17 | Answer; 18 | Record(${mfile}.wav,,0,skip); 19 | System(/usr/bin/lame -m m --cbr -b 32 "${mfile}.wav" "${mfile}.mp3" && /bin/rm -f "${mfile}.wav"); 20 | 21 | return; 22 | } // macro monitor(file) 23 | 24 | macro beep() { // Record ringing "beep" 25 | Answer; 26 | Monitor(alaw,/tmp/ast/beep); 27 | Wait(1); 28 | Ringing(); 29 | Wait(60); 30 | StopMonitor; 31 | return; 32 | } 33 | 34 | macro RecordLine(Line) { // Record remote auto-informer message 35 | if ("${Line}"="%DID%") Set(LOCAL(Line)=${leg2}); 36 | Answer; 37 | Monitor(alaw,/tmp/ast/${Line},b); 38 | &RouteCall(${Line}, 3); 39 | StopMonitor; 40 | return; 41 | } 42 | -------------------------------------------------------------------------------- /PBX/ael/func/MoreAnswer.ael: -------------------------------------------------------------------------------- 1 | // Rate extensions in CallList by last answer and make dial through it 2 | macro MoreAnswer(CallList,Simultaneous,FallBack,NoTransfer) { 3 | NoOp(${leg1}>>${leg2} FUNCTION MoreAnswer(${CallList},${Simultaneous},${FallBack},${NoTransfer})); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | hang=1; 6 | return; 7 | } 8 | RESULT=OK; 9 | 10 | Set(HASH(list)=${ODBC_CALLLIST(${HASH(res,CallList)})}); 11 | Set(_SPAWN=${HASH(res,Exten)}); 12 | 13 | local count=0; 14 | local rt=; 15 | Set(_SPAWN=${cid}); 16 | Set(_CALL_LIST=${CallList}); 17 | Set(LOCAL(calls)=${ODBC_SORT_CALLS(${BIND}_${CallList},${CallList})}); 18 | while (1=1) { 19 | Set(HASH(call)=${ODBC_FETCH(${calls})}); 20 | if ("${ODBC_FETCH_STATUS}"!="SUCCESS") { 21 | if ("${rt:1}"!="") Dial(${rt:1},${HASH(list,Timeout)},rfg); // Spawn parallel calls 22 | break; 23 | } 24 | Set(LOCAL(rt)=${rt}&LOCAL/${HASH(call,Exten)}:${HASH(call,NRec)}@spawn1/n); 25 | if (${count}<${Simultaneous}) { 26 | local count=${count}+1; 27 | } else { 28 | local count=0; 29 | Dial(${rt:1},${HASH(list,Timeout)},rfg); // Spawn parallel calls 30 | if ("${DIALSTATUS}"="ANSWER") break; 31 | local rt=; 32 | } 33 | } 34 | 35 | if ("${DIALSTATUS}"!="ANSWER") { 36 | Set(LOCAL(fn)=${FallBack}); 37 | if ("${fn}"="") return; 38 | #include "ael/func/FUNC.INC" 39 | } 40 | if ("${DIALSTATUS}"="ANSWER") RESULT=OK; else Set(RESULT=${DIALSTATUS}); 41 | return; 42 | } // macro MoreAnswer(CallList,Simultaneous,FallBack,NoTransfer) 43 | 44 | 45 | context spawn1 { // Spawn call with optional timeout before 46 | h => { 47 | Hangup(); 48 | } 49 | _. => { 50 | Dial(LOCAL/${SPAWN}-${CUT(EXTEN,:,1)}@out/n,,rfgU(SortCalls^${CALL_LIST}^${CUT(EXTEN,:,2)})); 51 | } 52 | } 53 | 54 | macro SortCalls() { 55 | catch s { 56 | NoOp(XX ${ARG1} ${ARG2}); 57 | Set(ODBC_SORT_CALLS(${BIND}_${ARG1})=${ARG2}); 58 | return; 59 | } 60 | return; 61 | } 62 | -------------------------------------------------------------------------------- /PBX/ael/func/NewCID.ael: -------------------------------------------------------------------------------- 1 | // Mangle CID for some purposes. Then dial some number. Dangerously! 2 | // insert into "Func" values ('090','28583','DialNewCID','28585','28531'); 3 | macro DialNewCID(NewCID,Number) { 4 | NoOp(${leg1}>>${leg2} FUNCTION DialNewCID(${NewCID},${Number})); 5 | catch h { // without a catch, dialplan stops execution on hangup !!! 6 | hang=1; 7 | return; 8 | } 9 | Set(RESULT=Invalid NewCID); 10 | if ("${NewCID}"="") return; 11 | 12 | Set(RESULT=Invalid Number); 13 | if ("${Number}"="") return; 14 | 15 | Set(CALLERID(num)=${NewCID}); 16 | 17 | Set(CHANNEL(language)=${DEFAULT_LANG}); 18 | Dial(LOCAL/${Number}@iax/n,,fg); 19 | 20 | return; 21 | } // macro DialNewCID(NewCID,Number) 22 | 23 | // Mangle CID for some purposes. Then call Func. Dangerously! 24 | // insert into "Func" values ('090','28583','NewCID','28585','process_prefix()'); 25 | macro NewCID(NewCID,Func) { 26 | NoOp(${leg1}>>${leg2} FUNCTION NewCID(${NewCID},${Func})); 27 | catch h { // without a catch, dialplan stops execution on hangup !!! 28 | hang=1; 29 | return; 30 | } 31 | Set(RESULT=Invalid NewCID); 32 | if ("${NewCID}"="") return; 33 | 34 | Set(RESULT=Invalid Func); 35 | if ("${Func}"="") return; 36 | 37 | Set(CALLERID(num)=${NewCID}); 38 | Set(CALLERID(ani)=${NewCID}); 39 | &call-entry(${from}); 40 | // Set(incoming=0); 41 | 42 | Set(LOCAL(fnc)=${CUT(Func,\(,1)}); 43 | Set(LOCAL(arg)=${CUT(Func,\(,2)}); 44 | Set(LOCAL(arg)=${CUT(arg,\),1)}); 45 | 46 | // Set(LOCAL(args)=${REPLACE(arg,\|,\,)}); Don't work in 1.6.2 47 | Set(LOCAL(args)=${CUT(arg,|,1)}); 48 | for (LOCAL(i)=2; ${i} <= ${FIELDQTY(arg,|)}; LOCAL(i)=${i}+1) { 49 | Set(LOCAL(args)=${args},${CUT(arg,|,${i})}); 50 | } 51 | &${fnc}(${args}); 52 | return; 53 | } // macro NewCID(NewCID,Func) 54 | -------------------------------------------------------------------------------- /PBX/ael/func/Notify.ael: -------------------------------------------------------------------------------- 1 | // insert into "Func" values ('090','ALL','NotifyURL','10.0.0.2:80'); 2 | macro NotifyURL(URL) { 3 | NoOp(${leg1}>>${leg2} FUNCTION NotifyURL(${URL})); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | hang=1; 6 | return; 7 | } 8 | Set(CURLOPT(httptimeout)=5); 9 | Set(LOCAL(time)=${STRFTIME(${EPOCH},,%d.%m.%Y %H:%M:%S)}); 10 | Set(HASH(QUERY)=${ODBC_ABC(${leg1})}); 11 | Set(LOCAL(city)=${HASH(QUERY,Region)}); 12 | Set(LOCAL(operator)=${HASH(QUERY,Owner)}); 13 | Set(LOCAL(abcdef)=${HASH(QUERY,ABCDEF)}); 14 | Set(LOCAL(direcion)=${HASH(QUERY,Direction)}); 15 | 16 | 17 | Set(LOCAL(res)=${CURL(http://${URL},cid=${leg1}&time=${time}&operator=${operator}&city=${city}&abcdef=${abcdef}&direcion=${direcion})}); 18 | RESULT=OK; 19 | return; 20 | } // macro NotifyURL(URL) 21 | -------------------------------------------------------------------------------- /PBX/ael/func/Operator.ael: -------------------------------------------------------------------------------- 1 | // Try to reach ${OPERATOR} if set, or ${FallBack} if no 2 | macro Operator(FallBack,NoTransfer) { 3 | NoOp(${leg1}>>${leg2} FUNCTION Operator(${FallBack},${NoTransfer})); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | hang=1; 6 | return; 7 | } 8 | Set(DATA=${DATA},FUNC=${CONTEXT};OPERATOR=${OPERATOR}); 9 | Set(CDR(x-data)={${DATA:1}}); 10 | 11 | RESULT=OK; 12 | if ("${OPERATOR}"!="") { 13 | Set(__dnid=${OPERATOR}); 14 | &Event(transfer); 15 | Dial(LOCAL/${OPERATOR}@iax/n); 16 | Set(RESULT=${DIALSTATUS}); 17 | } 18 | 19 | if ("${DIALSTATUS}"!="ANSWER") Dial(LOCAL/${FallBack}@iax/n); 20 | if ("${DIALSTATUS}"="ANSWER") RESULT=OK; else Set(RESULT=${DIALSTATUS}); 21 | return; 22 | } // macro Operator(FallBack,NoTransfer) 23 | -------------------------------------------------------------------------------- /PBX/ael/func/OrderCall.ael: -------------------------------------------------------------------------------- 1 | // Order the call to current did for ${Ext} 2 | macro OrderCall(Ext,NoAnswer) { 3 | NoOp(${leg1}>>${leg2} FUNCTION OrderCall(${Ext},${NoAnswer})); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | hang=1; 6 | return; 7 | } 8 | Set(DATA=${DATA},FUNC=${CONTEXT};Ext=${Ext};DID=${dnid}); 9 | Set(CDR(x-data)={${DATA:1}}); 10 | 11 | if ("${Ext}"!="") { 12 | System(/ast/call.pl ${Ext} ${dnid} ${BIND}); 13 | } 14 | 15 | if ("${NoAnswer}"!="1") { 16 | Answer; 17 | } 18 | Hangup; 19 | return; 20 | } // macro OrderCall(Ext,NoAnswer) 21 | -------------------------------------------------------------------------------- /PBX/ael/func/Pause.ael: -------------------------------------------------------------------------------- 1 | // Spawn a parallel calls to numbers in ${Route} after ${Timeout} seconds 2 | // Try to call ${FallBack} number when no answer was detected 3 | // Returns "OK" when Dial() succeeds, or explanation string in other case 4 | // insert into "Func" values ('090','ALL','DialPause','15','583:584:585','531','0'); 5 | 6 | macro DialPause(Timeout,Route,FallBack,NoTransfer) { 7 | NoOp(${leg1}>>${leg2} FUNCTION DialPause(${Timeout},${Route},${FallBack},${NoTransfer})); 8 | catch h { // without a catch, dialplan stops execution on hangup !!! 9 | hang=1; 10 | return; 11 | } 12 | 13 | Set(RESULT=Invalid Timeout); 14 | if ("${Timeout}"="") return; 15 | if (${Timeout}<0) return; 16 | Set(RESULT=Invalid Route); 17 | if ("${Route}"="") return; 18 | Set(__NO_TRANSFER=${NoTransfer}); 19 | 20 | RESULT=OK; 21 | 22 | for (LOCAL(i)=1; ${i}<=${FIELDQTY(Route,:)}; LOCAL(i)=${i}+1) { 23 | Set(LOCAL(rt)=${rt}&LOCAL/${CUT(Route,:,${i})}@iax/n); 24 | } 25 | Wait(${Timeout}); // RTFM Wait()!!! 26 | 27 | Set(CHANNEL(language)=${DEFAULT_LANG}); 28 | Dial(${rt:1},,rfg); // Spawn parallel calls 29 | 30 | if ("${DIALSTATUS}"!="ANSWER") { 31 | if ("${FallBack}"!="") Dial(LOCAL/${FallBack}@iax/n,,fg); 32 | } 33 | 34 | if ("${DIALSTATUS}"="ANSWER") RESULT=OK; else Set(RESULT=${DIALSTATUS}); 35 | return; 36 | } // DialPause(Timeout,Route,FallBack,NoTransfer) 37 | 38 | 39 | // The same as DialPause, except of ${Route} specifies full one 40 | // insert into "Func" values ('090','ALL','DialPause','15','583@iax:584@iax:28583-28585@out','531@iax','0'); 41 | macro RoutePause(Timeout,Route,FallBack,NoTransfer) { 42 | NoOp(${leg1}>>${leg2} FUNCTION RoutePause(${Timeout},${Route},${FallBack},${NoTransfer})); 43 | catch h { // without a catch, dialplan stops execution on hangup !!! 44 | hang=1; 45 | return; 46 | } 47 | 48 | Set(RESULT=Invalid Timeout); 49 | if ("${Timeout}"="") return; 50 | if (${Timeout}<0) return; 51 | Set(RESULT=Invalid Route); 52 | if ("${Route}"="") return; 53 | Set(__NO_TRANSFER=${NoTransfer}); 54 | 55 | RESULT=OK; 56 | 57 | for (LOCAL(i)=1; ${i}<=${FIELDQTY(Route,:)}; LOCAL(i)=${i}+1) { 58 | Set(LOCAL(rt)=${rt}&LOCAL/${CUT(Route,:,${i})}/n); 59 | } 60 | Wait(${Timeout}); // RTFM Wait()!!! 61 | 62 | Set(CHANNEL(language)=${DEFAULT_LANG}); 63 | Dial(${rt:1},,rfg); // Spawn parallel calls 64 | 65 | if ("${DIALSTATUS}"!="ANSWER") { 66 | if ("${FallBack}"!="") Dial(LOCAL/${FallBack}/n,,fg); 67 | } 68 | 69 | if ("${DIALSTATUS}"="ANSWER") RESULT=OK; else Set(RESULT=${DIALSTATUS}); 70 | return; 71 | } // RoutePause(Timeout,Route,FallBack,NoTransfer) 72 | 73 | // Calls func after ${Timeout} seconds. There may not be parallel call, 74 | // so next func may be called only after previous returns (with no success) 75 | // Returns "OK" when Func RESULT=OK, or explanation string in other case 76 | // Try to call ${FallBack} func in case of all ${Func} falls 77 | // insert into "Func" values ('090','ALL','Pause','15','_Dial(LOCAL/28583@iax/n|300|TtKkfg):_Dial(LOCAL/28584@iax/n|300|TtKkfg)','_Dial(LOCAL/28531@iax/n|300|TtKkfg)'); 78 | 79 | macro Pause(Timeout,Func,FallBack,NoTransfer) { 80 | NoOp(${leg1}>>${leg2} FUNCTION Pause(${Timeout},${Func},${FallBack},${NoTransfer})); 81 | catch h { // without a catch, dialplan stops execution on hangup !!! 82 | hang=1; 83 | return; 84 | } 85 | 86 | Set(RESULT=Invalid Timeout); 87 | if ("${Timeout}"="") return; 88 | if (${Timeout}<0) return; 89 | Set(RESULT=Invalid Route); 90 | if ("${Route}"="") return; 91 | Set(__NO_TRANSFER=${NoTransfer}); 92 | 93 | RESULT=OK; 94 | 95 | Wait(${Timeout}); // RTFM Wait()!!! 96 | 97 | for (LOCAL(i)=0; ${i} < ${FIELDQTY(Func,:)}; LOCAL(i)=${i}+1) { 98 | Set(LOCAL(fn)=${CUT(Func,:,${i})}); 99 | Set(LOCAL(fnc)=${CUT(fn,\(,1)}); 100 | Set(LOCAL(arg)=${CUT(fn,\(,2)}); 101 | Set(LOCAL(arg)=${CUT(arg,\),1)}); 102 | 103 | // Set(LOCAL(args)=${REPLACE(arg,\|,\,)}); Don't work in 1.6.2 104 | Set(LOCAL(args)=${CUT(arg,|,1)}); 105 | for (LOCAL(j)=2; ${j} <= ${FIELDQTY(arg,|)}; LOCAL(j)=${j}+1) { 106 | Set(LOCAL(args)=${args},${CUT(arg,|,${j})}); 107 | } 108 | &${fnc}(${args}); 109 | NoOp(RESULT=${RESULT}); 110 | 111 | if ("${RESULT}"="OK") break; 112 | } 113 | 114 | if (("${RESULT}"!="OK") & ("${FallBack}"!="")) { 115 | Set(LOCAL(fnc)=${CUT(FallBack,\(,1)}); 116 | Set(LOCAL(arg)=${CUT(FallBack,\(,2)}); 117 | Set(LOCAL(arg)=${CUT(arg,\),1)}); 118 | 119 | // Set(LOCAL(args)=${REPLACE(arg,\|,\,)}); Don't work in 1.6.2 120 | Set(LOCAL(args)=${CUT(arg,|,1)}); 121 | for (LOCAL(j)=2; ${j} <= ${FIELDQTY(arg,|)}; LOCAL(j)=${j}+1) { 122 | Set(LOCAL(args)=${args},${CUT(arg,|,${j})}); 123 | } 124 | &${fnc}(${args}); 125 | NoOp(RESULT=${RESULT}); 126 | } 127 | return; 128 | } // Pause(Timeout,Func,FallBack,NoTransfer) 129 | -------------------------------------------------------------------------------- /PBX/ael/func/PickupGroup.ael: -------------------------------------------------------------------------------- 1 | // Just try to pickup the group 2 | macro PickupGroup() { 3 | NoOp(${leg1}>>${leg2} FUNCTION PickupGroup()); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | hang=1; 6 | return; 7 | } 8 | Set(DATA=${DATA},FUNC=${CONTEXT}); 9 | Set(CDR(x-data)={${DATA:1}}); 10 | 11 | Set(CHANNEL(language)=${DEFAULT_LANG}); 12 | 13 | Set(SHARED(PICKUP)=${cid}); 14 | 15 | Dial(LOCAL/s@pickup_group,,Cg); 16 | // ForkCDR(r); 17 | Pickup(); 18 | return; 19 | } 20 | -------------------------------------------------------------------------------- /PBX/ael/func/PinCode.ael: -------------------------------------------------------------------------------- 1 | // Ask for pin. Call appropriate route when found 2 | // Spec = pin1>r1:pin2>r2:...:pinN>rN 3 | // insert into "Func" values ('090','ALL','DialPinCode','123>28584:321>28585:>28531'); 4 | macro DialPinCode(Spec) { 5 | NoOp(${leg1}>>${leg2} FUNCTION DialPinCode(${Spec})); 6 | catch h { // without a catch, dialplan stops execution on hangup !!! 7 | hang=1; 8 | return; 9 | } 10 | Set(RESULT=Invalid Spec); 11 | if ("${Spec}"="") return; 12 | 13 | Answer; 14 | Set(CHANNEL(language)=${DEFAULT_LANG}); 15 | local num=; 16 | Read(LOCAL(num),vm-password,12,,,3); 17 | 18 | for (LOCAL(i)=1; ${i}<=${FIELDQTY(Spec,:)}; LOCAL(i)=${i}+1) { 19 | Set(LOCAL(sp)=${CUT(Spec,:,${i})}); 20 | 21 | if ("${CUT(sp,>,1)}"="${num}") 22 | { 23 | Set(LOCAL(rt)=${CUT(sp,>,2)}); 24 | if ("${rt}"="") 25 | { 26 | Set(RESULT=Empty route); 27 | return; 28 | } 29 | Dial(LOCAL/${rt}@iax/n,,fg); 30 | Set(RESULT=${DIALSTATUS}); 31 | return; 32 | } 33 | } 34 | Set(RESULT=Invalid PIN); 35 | Playback(conf-invalidpin); 36 | 37 | return; 38 | } // macro DialPinCode(Spec) 39 | 40 | // Ask for pin. Call appropriate func when found 41 | // Spec = pin1>func1:pin2>func2:...:pinN>funcN 42 | // insert into "Func" values ('090','ALL','PinCode','123>_Dial(LOCAL/28584@iax/n|300|TtKkfg):321>_Dial(LOCAL/28585@iax/n|300|TtKkfg):>_Dial(LOCAL/28531@iax/n|300|TtKkfg)'); 43 | macro PinCode(Spec) { 44 | NoOp(${leg1}>>${leg2} FUNCTION PinCode(${Spec})); 45 | catch h { // without a catch, dialplan stops execution on hangup !!! 46 | hang=1; 47 | return; 48 | } 49 | Set(RESULT=Invalid Spec); 50 | if ("${Spec}"="") return; 51 | 52 | Answer; 53 | Set(CHANNEL(language)=${DEFAULT_LANG}); 54 | local num=; 55 | Read(LOCAL(num),vm-password,12,,,3); 56 | 57 | for (LOCAL(i)=1; ${i} <= ${FIELDQTY(Spec,:)}; LOCAL(i)=${i}+1) { 58 | Set(LOCAL(sp)=${CUT(Spec,:,${i})}); 59 | 60 | if ("${CUT(sp,>,1)}"="${num}") 61 | { 62 | Set(LOCAL(fn)=${CUT(sp,>,2)}); 63 | if ("${fn}"="") 64 | { 65 | Set(RESULT=Empty func); 66 | return; 67 | } 68 | 69 | Set(LOCAL(fnc)=${CUT(fn,\(,1)}); 70 | Set(LOCAL(arg)=${CUT(fn,\(,2)}); 71 | Set(LOCAL(arg)=${CUT(arg,\),1)}); 72 | 73 | // Set(LOCAL(args)=${REPLACE(arg,\|,\,)}); Don't work in 1.6.2 74 | Set(LOCAL(args)=${CUT(arg,|,1)}); 75 | for (LOCAL(j)=2; ${j} <= ${FIELDQTY(arg,|)}; LOCAL(j)=${j}+1) { 76 | Set(LOCAL(args)=${args},${CUT(arg,|,${j})}); 77 | } 78 | 79 | &${fnc}(${args}); 80 | NoOp(RESULT=${RESULT}); 81 | return; 82 | } 83 | } // for i 84 | 85 | Set(RESULT=Invalid PIN); 86 | Playback(conf-invalidpin); 87 | 88 | return; 89 | } // macro PinCode(Spec) 90 | -------------------------------------------------------------------------------- /PBX/ael/func/PinEvent.ael: -------------------------------------------------------------------------------- 1 | // Enter pin-code and send it via HTTP POST event channel 2 | macro PinEvent(MaxDigits) { 3 | NoOp(${leg1}>>${leg2} FUNCTION PinEvent(${MaxDigits})); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | hang=1; 6 | return; 7 | } 8 | if ("${URL}"="") { 9 | return; 10 | } 11 | if ("${MaxDigits}"="") { 12 | local MaxDigits=4; 13 | } 14 | 15 | Set(CHANNEL(language)=${DEFAULT_LANG}); 16 | local num=; 17 | Read(LOCAL(num),vm-password,${MaxDigits},,,5); 18 | 19 | Set(CURLOPT(httptimeout)=5); 20 | Set(LOCAL(time)=${STRFTIME(${EPOCH},,%d.%m.%Y %H:%M:%S)}); 21 | 22 | // NoOp(Event(${URL},linkedid=${uniqueid}&id=${UNIQUEID}&time=${time}&event=pincode&pincode=${num}&cid=${cid}&did=${dnid}&uuid=${UUID})); 23 | // Set(LOCAL(res)=${CURL(${URL},linkedid=${uniqueid}&id=${UNIQUEID}&time=${time}&event=pincode&pincode=${num}&cid=${cid}&did=${dnid}&uuid=${UUID})}); 24 | // curl -w "%{http_code}" --data 'action=dnd&cid=206&enable=0&check=1' 188.227.101.17:8080 25 | NoOp(SHELL(curl -w "%{http_code}" --data 'linkedid=${uniqueid}&id=${UNIQUEID}&time=${time}&event=pincode&pincode=${num}&cid=${cid}&did=${dnid}&uuid=${UUID}' ${URL})); 26 | Set(LOCAL(res)=${SHELL(curl -w "%{http_code}" --data 'linkedid=${uniqueid}&id=${UNIQUEID}&time=${time}&event=pincode&pincode=${num}&cid=${cid}&did=${dnid}&uuid=${UUID}' ${URL})}); 27 | 28 | if ("${res}"="200") { 29 | Set(ODBC_EXTEN(${HASH(ext,Exten)},${HASH(ext,BIND)},Enabled)=1); 30 | } 31 | 32 | Set(DATA=${DATA},FUNC=${CONTEXT};RES=${res}); 33 | Set(CDR(x-data)={${DATA:1}}); 34 | 35 | RESULT=OK; 36 | return; 37 | } // macro Event(event) 38 | -------------------------------------------------------------------------------- /PBX/ael/func/PlayFiles.ael: -------------------------------------------------------------------------------- 1 | // Play ${File} ${Count} times (if set) 2 | // insert into "Func" values ('090','ALL',NULL,'PlayFiles','announce1&announce2'); 3 | 4 | macro PlayFiles(File,Count,NoAnswer,fn) { 5 | NoOp(${leg1}>>${leg2} FUNCTION PlayFiles(${File},${Count},${NoAnswer},${fn})); 6 | catch h { // without a catch, dialplan stops execution on hangup !!! 7 | hang=1; 8 | return; 9 | } 10 | 11 | Set(DATA=${DATA},FUNC=${CONTEXT}); 12 | Set(CDR(x-data)={${DATA:1}}); 13 | 14 | Set(RESULT=Invalid File); 15 | if ("${File}"="") return; 16 | 17 | Set(CHANNEL(language)=${DEFAULT_LANG}); 18 | RESULT=OK; 19 | 20 | if ("${NoAnswer}"!="1") { 21 | Answer; 22 | } else { 23 | Progress; 24 | } 25 | 26 | Wait(1); 27 | 28 | local i=0; 29 | if ("${count}"="") { 30 | local count=1; 31 | } 32 | while (${i} < ${count}) { 33 | Playback(${File},noanswer); 34 | Wait(1); 35 | local i=${i}+1; 36 | } 37 | 38 | #include "ael/func/FUNC.INC" 39 | return; 40 | } // macro PlayFiles(File,Count,NoAnswer,fn) 41 | -------------------------------------------------------------------------------- /PBX/ael/func/QueueAgent.ael: -------------------------------------------------------------------------------- 1 | // Add|unpause queue agent 2 | // insert into "Func" values ('090','ALL',NULL,'AgentOn','3370303','444','3370303*444','Func(090.1)'); 3 | // Q*459*3370303 4 | macro AgentOn(Queue,Line,Device,fn) { 5 | NoOp(${leg1}>>${leg2} FUNCTION AgentOn(${Queue},${Line},${Device},${fn})); 6 | catch h { // without a catch, dialplan stops execution on hangup !!! 7 | hang=1; 8 | return; 9 | } 10 | if ("${from}"="transfer") { // Pass transfers to queue 11 | &queue(${Queue},,,); 12 | return; 13 | } 14 | 15 | Answer; 16 | if ("${Line}"="") Set(LOCAL(Line)=${cid}); 17 | if ("${Device}"="") Set(LOCAL(Device)=${dnid}); 18 | DEVICE_STATE(Custom:${Device})=INUSE; 19 | 20 | Set(ODBC_EXTEN(${Line},${BIND},DND)=0); 21 | ClearHash(ext); 22 | Set(HASH(ext)=${ODBC_EXTEN(${Line})},${BIND}); 23 | if ("${BIND}"!="") Set(LOCAL(Line)=!${BIND}+${Line}); 24 | 25 | // AddQueueMember(${Queue},LOCAL/${Line}@iax/n,3,,${Queue}*${Line},hint:Q*${Line}*${Queue}@blf); // Make distinct slots for upper queues 26 | AddQueueMember(${Queue},LOCAL/${Line}@iax/n,,,${Queue}*${Line}); 27 | Set(RESULT=${AQMSTATUS}); 28 | if ("${RESULT}"!="NOSUCHQUEUE") { 29 | UnpauseQueueMember(${Queue},LOCAL/${Line}@iax/n,,AgentOn); 30 | Set(RESULT=${UPQMSTATUS}); 31 | if ("${RESULT}"="UNPAUSED") RESULT=OK; 32 | } else RESULT=ERR; 33 | 34 | NoOp(RESULT=${RESULT}); 35 | 36 | if ("${fn}"="") return; 37 | #include "ael/func/FUNC.INC" 38 | return; 39 | } // macro AgentOn(Queue,Line,Device,fn) 40 | 41 | // Remove|pause queue agent 42 | // insert into "Func" values ('090','ALL',NULL,'AgentOff','3370303','444','3370303*444','Func(090.1)'); 43 | macro AgentOff(Queue,Line,Device,fn) { 44 | NoOp(${leg1}>>${leg2} FUNCTION AgentOff(${Queue},${Line},${Device},${fn})); 45 | catch h { // without a catch, dialplan stops execution on hangup !!! 46 | hang=1; 47 | return; 48 | } 49 | if ("${Line}"="") Set(LOCAL(Line)=${cid}); 50 | if ("${Device}"="") Set(LOCAL(Device)=${Queue}*${cid}); 51 | DEVICE_STATE(Custom:${Device})=NOT_INUSE; 52 | 53 | if ("${BIND}"!="") Set(LOCAL(Line)=!${BIND}+${Line}); 54 | RemoveQueueMember(${Queue},LOCAL/${Line}@iax/n); 55 | Set(RESULT=${RQMSTATUS}); 56 | if ("${RESULT}"!="REMOVED") { 57 | PauseQueueMember(${Queue},LOCAL/${Line}@iax/n,,AgentOff); 58 | Set(RESULT=${PQMSTATUS}); 59 | if ("${RESULT}"="PAUSED") RESULT=OK; 60 | } else RESULT=OK; 61 | 62 | NoOp(RESULT=${RESULT}); 63 | 64 | if ("${fn}"="") return; 65 | #include "ael/func/FUNC.INC" 66 | return; 67 | } // macro AgentOff(Queue,Line,Device,fn) 68 | 69 | macro AgentSwitch(Queue,Line,Device,fn) { 70 | NoOp(${leg1}>>${leg2} FUNCTION AgentSwitch(${Queue},${Line},${Device},${fn})); 71 | catch h { // without a catch, dialplan stops execution on hangup !!! 72 | hang=1; 73 | return; 74 | } 75 | if ("${from}"="transfer") { // Pass transfers to queue 76 | &queue(${Queue},,,); 77 | return; 78 | } 79 | 80 | Answer; 81 | if ("${Line}"="") Set(LOCAL(Line)=${cid}); 82 | if ("${Device}"="") Set(LOCAL(Device)=${dnid}); 83 | if ("${DEVICE_STATE(Custom:${Device})}"="INUSE") { 84 | &AgentOff(${Queue},${Line},${Device},${fn}); 85 | } else { 86 | &AgentOn(${Queue},${Line},${Device},${fn}); 87 | } 88 | 89 | return; 90 | } 91 | 92 | // Call function "f1" if there are more then "Threshold" agents in queue, "f2" otherwise 93 | // insert into "Func" values ('090','ALL',NULL,'QueueActive','3300058','_Dial(LOCAL/28584@iax/n|10|TtKkfgm)','_Dial(LOCAL/28585@iax/n|10|TtKkfgm)','0'); 94 | macro QueueActive(QueueName,f1,f2,Threshold) { 95 | NoOp(${leg1}>>${leg2} FUNCTION QueueActive(${QueueName},${f1},${f2},${Threshold})); 96 | catch h { // without a catch, dialplan stops execution on hangup !!! 97 | hang=1; 98 | return; 99 | } 100 | 101 | Set(RESULT=Invalid QueueName); 102 | if ("${QueueName}"="") return; 103 | Set(RESULT=Invalid Func); 104 | if ("${f1}"="") return; 105 | if ("${f2}"="") Set(f2=${f1}); 106 | 107 | if ("${Threshold}"="") Set(Threshold=0); 108 | 109 | RESULT=OK; 110 | 111 | Set(LOCAL(fn)=${f2}); 112 | if (${QUEUE_MEMBER(${name},logged)}>${Threshold}) Set(LOCAL(fn)=${f1}); 113 | 114 | #include "ael/func/FUNC.INC" 115 | return; 116 | } // macro QueueActive(QueueName,f1,f2,Threshold) 117 | -------------------------------------------------------------------------------- /PBX/ael/func/RoundRobin.ael: -------------------------------------------------------------------------------- 1 | // Takes a route by the round-robin algorithm 2 | // Records last choise on ${RR} global 3 | // Returns "OK" when Dial() succeeds, or explanation string in other case 4 | // insert into "Func" values ('090','ALL','DialRoundRobin','RR-mark:10','583:584:585','531','1'); 5 | 6 | macro DialRoundRobin(RR,Route,FallBack,NoTransfer) { 7 | NoOp(${leg1}>>${leg2} FUNCTION DialRoundRobin(${RR},${Route},${FallBack},${NoTransfer})); 8 | catch h { // without a catch, dialplan stops execution on hangup !!! 9 | hang=1; 10 | return; 11 | } 12 | 13 | Set(RESULT=Invalid RR); 14 | if ("${RR}"="") return; 15 | Set(RESULT=Invalid Route); 16 | if ("${Route}"="") return; 17 | Set(__NO_TRANSFER=${NoTransfer}); 18 | Set(__IN_QUEUE=1); 19 | Set(CHANNEL(language)=${DEFAULT_LANG}); 20 | 21 | RESULT=OK; 22 | 23 | Set(LOCAL(timeout)=${CUT(RR,:,2)}); 24 | if ("${timeout}"="") Set(LOCAL(timeout)=${ODBC_CONST(timeout-dial)}); 25 | if ("${timeout}"="") Set(LOCAL(timeout)=300); 26 | Set(LOCAL(RR)=${CUT(RR,:,1)}); 27 | 28 | if("${GLOBAL(${RR})}"!="") Set(LOCAL(pos)=${GLOBAL(${RR})}); else local pos=0; 29 | 30 | Set(DSTATUS=); 31 | 32 | Ringing(); 33 | for (LOCAL(i)=0; ${i}<${FIELDQTY(Route,:)}; LOCAL(i)=${i}+1) { 34 | local pos=${pos}+1; 35 | if (${pos}>${FIELDQTY(Route,:)}) local pos=1; 36 | Set(GLOBAL(${RR})=${pos}); 37 | 38 | Set(LOCAL(rt)=${CUT(Route,:,${pos})}); 39 | Dial(LOCAL/${rt}@iax/n,${timeout},fg); 40 | 41 | Set(DSTATUS=${DSTATUS}-${DIALSTATUS}); 42 | // NoOp(HANGUPCAUSE=${HANGUPCAUSE} ${DS}); 43 | 44 | if ("${DIALSTATUS}"="ANSWER") break; 45 | } 46 | Set(__IN_QUEUE=); 47 | 48 | if (${REGEX("BUSY" ${DSTATUS})}=1) { 49 | Set(DIALSTATUS=BUSY); 50 | } else { 51 | if (${REGEX("NOANSWER" ${DSTATUS})}=1) { 52 | Set(DIALSTATUS=NOANSWER); 53 | } 54 | } 55 | 56 | if ("${DIALSTATUS}"!="ANSWER"&"${DIALSTATUS}"!="BUSY") { 57 | if ("${FallBack}"!="") Dial(LOCAL/${FallBack}@iax/n,${timeout},fg); 58 | Set(DSTATUS=${DSTATUS}-${DIALSTATUS}); 59 | } 60 | 61 | if (${REGEX("BUSY" ${DSTATUS})}=1) { 62 | Set(DIALSTATUS=BUSY); 63 | } else { 64 | if (${REGEX("NOANSWER" ${DSTATUS})}=1) { 65 | Set(DIALSTATUS=NOANSWER); 66 | } 67 | } 68 | 69 | if ("${DIALSTATUS}"="ANSWER") RESULT=OK; else Set(RESULT=${DIALSTATUS}); 70 | return; 71 | } // DialRoundRobin(RR,Route,FallBack,NoTransfer) 72 | 73 | // Takes a func to call by the round-robin algorithm 74 | // Records last choise on ${RR} global 75 | // Returns "OK" when Func RESULT=OK, or explanation string in other case 76 | // Try to call ${FallBack} func in case of all ${RR} falls 77 | // insert into "Func" values ('090','ALL','RoundRobin','RR-mark','_Dial(LOCAL/28583@iax/n|300|TtKkfg):_Dial(LOCAL/28584@iax/n|300|TtKkfg)','_Dial(LOCAL/28531@iax/n|300|TtKkfg)'); 78 | 79 | macro RoundRobin(RR,Func,FallBack,NoTransfer) { 80 | NoOp(${leg1}>>${leg2} FUNCTION RoundRobin(${RR},${Func},${FallBack},${NoTransfer})); 81 | catch h { // without a catch, dialplan stops execution on hangup !!! 82 | hang=1; 83 | return; 84 | } 85 | 86 | Set(RESULT=Invalid RR); 87 | if ("${RR}"="") return; 88 | Set(RESULT=Invalid Route); 89 | if ("${Func}"="") return; 90 | Set(__NO_TRANSFER=${NoTransfer}); 91 | Set(__IN_QUEUE=1); 92 | 93 | RESULT=OK; 94 | 95 | if("${GLOBAL(${RR})}"!="") Set(LOCAL(pos)=${GLOBAL(${RR})}); else local pos=0; 96 | 97 | for (LOCAL(i)=0; ${i} < ${FIELDQTY(Func,:)}; LOCAL(i)=${i}+1) { 98 | local pos=${pos}+1; 99 | if (${pos}>${FIELDQTY(Func,:)}) local pos=1; 100 | Set(GLOBAL(${RR})=${pos}); 101 | 102 | Set(LOCAL(fn)=${CUT(Func,:,${pos})}); 103 | 104 | Set(DATA=${DATA},FUNC=${CONTEXT};POS=${pos}); 105 | Set(CDR(x-data)={${DATA:1}}); 106 | 107 | #include "ael/func/FUNC.INC" 108 | 109 | if ("${RESULT}"="OK") break; 110 | } 111 | Set(__IN_QUEUE=); 112 | 113 | if (("${RESULT}"!="OK") & ("${FallBack}"!="")) { 114 | Set(LOCAL(fn)=${FallBack}); 115 | 116 | Set(DATA=${DATA},FUNC=${CONTEXT};POS=F); 117 | Set(CDR(x-data)={${DATA:1}}); 118 | 119 | #include "ael/func/FUNC.INC" 120 | } 121 | return; 122 | } // macro RoundRobin(RR,Func,FallBack,NoTransfer) 123 | -------------------------------------------------------------------------------- /PBX/ael/func/Schedule.ael: -------------------------------------------------------------------------------- 1 | // Dial to "r1"|"r2", according to the schedule Spec 2 | // Use trigger with z-level to bypass shedule 3 | // insert into "Func" values ('090','ALL',NULL,'DialSchedule','3300058|0','531','585'); 4 | 5 | macro DialSchedule(Spec,r1,r2,ZT) { 6 | NoOp(${leg1}>>${leg2} FUNCTION DialSchedule(${Spec},${r1},${r2},${ZT})); 7 | catch h { // without a catch, dialplan stops execution on hangup !!! 8 | hang=1; 9 | return; 10 | } 11 | if ("${r2}"="") Set(r2=${r1}); 12 | 13 | Set(LOCAL(T)=${ODBC_VAR(${ZT})}); 14 | local num=${r2}; 15 | 16 | if ("${T}"="") { 17 | Set(LOCAL(qty)=${FIELDQTY(Spec,\|)}); 18 | for (LOCAL(i)=1; ${i}<=${qty}; LOCAL(i)=${i}+1) { 19 | Set(LOCAL(spc)=${CUT(Spec,\|,${i})}); 20 | 21 | if ("${spc:0:1}"="!") { // Cut BIND from extension spec 22 | Set(LOCAL(spc)=${spc:1}); 23 | Set(LOCAL(not)=1); 24 | } else { 25 | Set(LOCAL(not)=0); 26 | } 27 | 28 | Set(LOCAL(res)=${ODBC_SCHEDULE(${spc})}); 29 | if ("${res}${not}"="10"|"${res}${not}"="01") { 30 | Set(LOCAL(num)=${r1}); 31 | break; 32 | } 33 | } 34 | } else { 35 | if ("${T}"="1") { 36 | Set(LOCAL(num)=${r1}); 37 | } 38 | } 39 | Set(CHANNEL(language)=${DEFAULT_LANG}); 40 | Dial(LOCAL/${num}@iax/n,,fg); 41 | 42 | return; 43 | } // macro DialSchedule(Spec,r1,r2,ZT) 44 | 45 | // Call ${leg1}>>${leg2} FUNCTION "f1"|"f2", according to the schedule Spec 46 | // Use trigger with z-level to bypass shedule 47 | // insert into "Func" values ('090','ALL',NULL,'Schedule','3300058|0','_Dial(LOCAL/28584@iax/n|10|TtKkfgm)','_Dial(LOCAL/28585@iax/n|10|TtKkfgm)','ztWorkDays'); 48 | macro Schedule(Spec,f1,f2,ZT) { 49 | NoOp(${leg1}>>${leg2} FUNCTION Schedule(${Spec},${f1},${f2},${ZT})); 50 | catch h { // without a catch, dialplan stops execution on hangup !!! 51 | hang=1; 52 | return; 53 | } 54 | 55 | Set(RESULT=Invalid Spec); 56 | if ("${Spec}"="") return; 57 | Set(RESULT=Invalid Func); 58 | if ("${f1}"="") return; 59 | if ("${f2}"="") Set(f2=${f1}); 60 | 61 | RESULT=OK; 62 | 63 | Set(LOCAL(T)=${ODBC_VAR(${ZT})}); 64 | Set(LOCAL(fn)=${f2}); 65 | 66 | if ("${T}"="") { 67 | Set(LOCAL(qty)=${FIELDQTY(Spec,\|)}); 68 | for (LOCAL(i)=1; ${i}<=${qty}; LOCAL(i)=${i}+1) { 69 | Set(LOCAL(spc)=${CUT(Spec,\|,${i})}); 70 | 71 | if ("${spc:0:1}"="!") { // Cut BIND from extension spec 72 | Set(LOCAL(spc)=${spc:1}); 73 | Set(LOCAL(not)=1); 74 | } else { 75 | Set(LOCAL(not)=0); 76 | } 77 | 78 | Set(LOCAL(res)=${ODBC_SCHEDULE(${spc})}); 79 | if ("${res}${not}"="10"|"${res}${not}"="01") { 80 | Set(LOCAL(fn)=${f1}); 81 | break; 82 | } 83 | } 84 | } else { 85 | if ("${T}"="1") { 86 | Set(LOCAL(fn)=${f1}); 87 | } 88 | } 89 | 90 | Set(DATA=${DATA},FUNC=${CONTEXT};ZT=${T};IFTIME=${res}); 91 | Set(CDR(x-data)={${DATA:1}}); 92 | 93 | #include "ael/func/FUNC.INC" 94 | return; 95 | } // macro Schedule(Spec,f1,f2,ZT) 96 | -------------------------------------------------------------------------------- /PBX/ael/func/SendMessage.ael: -------------------------------------------------------------------------------- 1 | // Try to send message (e.g. SIP MESSAGE method) while dial 2 | // insert into "Func" values ('090','28583','DialSendMessage','Test message','531:583'); 3 | macro DialSendMessage(Message,Route,FallBack,NoTransfer) { 4 | NoOp(${leg1}>>${leg2} FUNCTION DialSendMessage(${Message},${Route},${FallBack},${NoTransfer})); 5 | catch h { // without a catch, dialplan stops execution on hangup !!! 6 | hang=1; 7 | return; 8 | } 9 | 10 | Set(RESULT=Invalid Route); 11 | if ("${Route}"="") return; 12 | 13 | if ("${Message}"!="") Set(__SEND_MESSAGE=${Message}); 14 | 15 | RESULT=OK; 16 | 17 | for (LOCAL(i)=1; ${i}<=${FIELDQTY(Route,:)}; LOCAL(i)=${i}+1) { 18 | Set(LOCAL(rt)=${rt}&LOCAL/${CUT(Route,:,${i})}@iax/n); 19 | } 20 | 21 | Set(__NO_TRANSFER=${NoTransfer}); 22 | Set(CHANNEL(language)=${DEFAULT_LANG}); 23 | Dial(${rt:1},,rfg); // Spawn parallel calls 24 | 25 | if ("${DIALSTATUS}"!="ANSWER") { 26 | if ("${FallBack}"!="") Dial(LOCAL/${FallBack}@iax/n,,fg); 27 | } 28 | 29 | if ("${DIALSTATUS}"="ANSWER") RESULT=OK; else Set(RESULT=${DIALSTATUS}); 30 | return; 31 | } // DialSendMessage(Message,Route,FallBack,NoTransfer) 32 | 33 | // The same as DialSendMessage, except of ${Route} specifies full one 34 | // insert into "Func" values ('090','28583','RouteSendMessage','Test message','531@iax:583@iax'); 35 | macro RouteSendMessage(Message,Route,FallBack,NoTransfer) { 36 | NoOp(${leg1}>>${leg2} FUNCTION RouteSendMessage(${Message},${Route},${FallBack},${NoTransfer})); 37 | catch h { // without a catch, dialplan stops execution on hangup !!! 38 | hang=1; 39 | return; 40 | } 41 | 42 | Set(RESULT=Invalid Route); 43 | if ("${Route}"="") return; 44 | 45 | if ("${Message}"!="") Set(__SEND_MESSAGE=${Message}); 46 | 47 | RESULT=OK; 48 | 49 | for (LOCAL(i)=1; ${i}<=${FIELDQTY(Route,:)}; LOCAL(i)=${i}+1) { 50 | Set(LOCAL(rt)=${rt}&LOCAL/${CUT(Route,:,${i})}/n); 51 | } 52 | 53 | Set(__NO_TRANSFER=${NoTransfer}); 54 | Set(CHANNEL(language)=${DEFAULT_LANG}); 55 | Dial(${rt:1},,rfg); // Spawn parallel calls 56 | 57 | if ("${DIALSTATUS}"!="ANSWER") { 58 | if ("${FallBack}"!="") Dial(LOCAL/${FallBack}@iax/n,,fg); 59 | } 60 | 61 | if ("${DIALSTATUS}"="ANSWER") RESULT=OK; else Set(RESULT=${DIALSTATUS}); 62 | return; 63 | } // RouteSendMessage(Message,Route,FallBack,NoTransfer) 64 | 65 | // insert into "Func" values ('090','ALL','SendMessage','Test message','_Dial(LOCAL/28583@iax/n|300|TtKkfg)'); 66 | macro SendMessage(Message,Func,FallBack,NoTransfer) { 67 | NoOp(${leg1}>>${leg2} FUNCTION SendMessage(${Message},${Func},${FallBack},${NoTransfer})); 68 | catch h { // without a catch, dialplan stops execution on hangup !!! 69 | hang=1; 70 | return; 71 | } 72 | 73 | Set(RESULT=Invalid Func); 74 | if ("${Func}"="") return; 75 | Set(__NO_TRANSFER=${NoTransfer}); 76 | 77 | if ("${Message}"!="") Set(__SEND_MESSAGE=${Message}); 78 | 79 | RESULT=OK; 80 | 81 | for (LOCAL(i)=1; ${i} <= ${FIELDQTY(Func,:)}; LOCAL(i)=${i}+1) { 82 | Set(LOCAL(fn)=${CUT(Func,:,${i})}); 83 | Set(LOCAL(fnc)=${CUT(fn,\(,1)}); 84 | Set(LOCAL(arg)=${CUT(fn,\(,2)}); 85 | Set(LOCAL(arg)=${CUT(arg,\),1)}); 86 | 87 | // Set(LOCAL(args)=${REPLACE(arg,\|,\,)}); Don't work in 1.6.2 88 | Set(LOCAL(args)=${CUT(arg,|,1)}); 89 | for (LOCAL(j)=2; ${j} <= ${FIELDQTY(arg,|)}; LOCAL(j)=${j}+1) { 90 | Set(LOCAL(args)=${args},${CUT(arg,|,${j})}); 91 | } 92 | &${fnc}(${args}); 93 | NoOp(RESULT=${RESULT}); 94 | 95 | if ("${RESULT}"="OK"|"${RESULT}"="NOANSWER") break; 96 | } 97 | 98 | if (("${RESULT}"!="OK") & ("${FallBack}"!="")) { 99 | Set(LOCAL(fnc)=${CUT(FallBack,\(,1)}); 100 | Set(LOCAL(arg)=${CUT(FallBack,\(,2)}); 101 | Set(LOCAL(arg)=${CUT(arg,\),1)}); 102 | 103 | // Set(LOCAL(args)=${REPLACE(arg,\|,\,)}); Don't work in 1.6.2 104 | Set(LOCAL(args)=${CUT(arg,|,1)}); 105 | for (LOCAL(j)=2; ${j} <= ${FIELDQTY(arg,|)}; LOCAL(j)=${j}+1) { 106 | Set(LOCAL(args)=${args},${CUT(arg,|,${j})}); 107 | } 108 | &${fnc}(${args}); 109 | NoOp(RESULT=${RESULT}); 110 | } 111 | return; 112 | } // macro SendMessage(Message,Func,FallBack,NoTransfer) 113 | -------------------------------------------------------------------------------- /PBX/ael/func/SetTransfer.ael: -------------------------------------------------------------------------------- 1 | // Set Exten.TransferCall 2 | macro SetTransfer(Exten,Transfer) { 3 | NoOp(${leg1}>>${leg2} FUNCTION SetTransfer(${Exten},${Transfer})); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | hang=1; 6 | return; 7 | } 8 | Set(RESULT=Invalid Exten); 9 | if ("${Exten}"="") Set(Exten=${XFR}); 10 | if ("${Exten}"="") return; 11 | 12 | Set(CHANNEL(language)=${DEFAULT_LANG}); 13 | 14 | if ("${Transfer}"="%CID%") Set(LOCAL(Transfer)=${leg1}); 15 | if ("${Transfer:1}"="%CID%") Set(LOCAL(Transfer)=${Transfer:0:1}${leg1}); 16 | 17 | Set(ODBC_EXTEN(${Exten},${BIND},TransferCall)=${Transfer}); 18 | if ("${Transfer}"!="") { 19 | &SayPhone(${Transfer}); 20 | } else Playback(ivr/transfer-off); 21 | 22 | return; 23 | } // macro SetTransfer(Exten,Transfer) 24 | -------------------------------------------------------------------------------- /PBX/ael/func/SrcRoute.ael: -------------------------------------------------------------------------------- 1 | // Get number to dial, accroding to src-routing table 2 | // insert into "Func" values ('090','ALL','DialSrcRoute','1','583:584'); 3 | macro DialSrcRoute(RoutingList,Default,FallBack,NoTransfer) { 4 | NoOp(${leg1}>>${leg2} FUNCTION DialSrcRoute(${RoutingList},${Default},${FallBack},${NoTransfer})); 5 | catch h { // without a catch, dialplan stops execution on hangup !!! 6 | hang=1; 7 | return; 8 | } 9 | Set(RESULT=Invalid RoutingList); 10 | if ("${RoutingList}"="") return; 11 | 12 | Set(RESULT=Invalid Route); 13 | 14 | Set(LOCAL(Route)=${ODBC_GET_ROUTE(${RoutingList},${cid})}); 15 | if ("${Route}"="") Set(LOCAL(Route)=${Default}); 16 | if ("${Route}"="") return; 17 | 18 | RESULT=OK; 19 | 20 | for (LOCAL(i)=1; ${i}<=${FIELDQTY(Route,:)}; LOCAL(i)=${i}+1) { 21 | Set(LOCAL(rt)=${rt}&LOCAL/${CUT(Route,:,${i})}@iax/n); 22 | } 23 | 24 | Set(__NO_TRANSFER=${NoTransfer}); 25 | Set(CHANNEL(language)=${DEFAULT_LANG}); 26 | Dial(${rt:1},,rfg); // Spawn parallel calls 27 | 28 | if ("${DIALSTATUS}"!="ANSWER") { 29 | if ("${FallBack}"!="") Dial(LOCAL/${FallBack}@iax/n,,fg); 30 | } 31 | 32 | if ("${DIALSTATUS}"="ANSWER") RESULT=OK; else Set(RESULT=${DIALSTATUS}); 33 | return; 34 | } // macro DialSrcRoute(RoutingList,Default,FallBack,NoTransfer) 35 | 36 | // The same as DialSrcRoute, except of ${Route} specifies full one 37 | // insert into "Func" values ('090','ALL','RouteSrcRoute','1','583@iax:584@iax'); 38 | macro RouteSrcRoute(RoutingList,Default,FallBack,NoTransfer) { 39 | NoOp(${leg1}>>${leg2} FUNCTION RouteSrcRoute(${RoutingList},${Default},${FallBack},${NoTransfer})); 40 | catch h { // without a catch, dialplan stops execution on hangup !!! 41 | hang=1; 42 | return; 43 | } 44 | Set(RESULT=Invalid RoutingList); 45 | if ("${RoutingList}"="") return; 46 | 47 | Set(RESULT=Invalid Route); 48 | 49 | Set(LOCAL(Route)=${ODBC_GET_ROUTE(${RoutingList},${cid})}); 50 | if ("${Route}"="") Set(LOCAL(Route)=${Default}); 51 | if ("${Route}"="") return; 52 | 53 | for (LOCAL(i)=1; ${i}<=${FIELDQTY(Route,:)}; LOCAL(i)=${i}+1) { 54 | Set(LOCAL(rt)=${rt}&LOCAL/${CUT(Route,:,${i})}/n); 55 | } 56 | 57 | Set(__NO_TRANSFER=${NoTransfer}); 58 | Set(CHANNEL(language)=${DEFAULT_LANG}); 59 | Dial(${rt:1},,rfg); // Spawn parallel calls 60 | 61 | if ("${DIALSTATUS}"!="ANSWER") { 62 | if ("${FallBack}"!="") Dial(LOCAL/${FallBack}@iax/n,,fg); 63 | } 64 | 65 | if ("${DIALSTATUS}"="ANSWER") RESULT=OK; else Set(RESULT=${DIALSTATUS}); 66 | return; 67 | } // macro RouteSrcRoute(RoutingList,Default,FallBack,NoTransfer) 68 | -------------------------------------------------------------------------------- /PBX/ael/func/Survey.ael: -------------------------------------------------------------------------------- 1 | // Implement survey on particular Exten to qualify the service quality 2 | // insert into "Func" values ('090','ALL','Survey','28583','1','digits/million:digits/milliona:digits/millionov',1); 3 | // Remember Delay must be 0 for Exten participated in survey! 4 | macro Survey(Exten,SurveyList,Prompts,NoTransfer) { 5 | NoOp(${leg1}>>${leg2} FUNCTION Survey(${Exten},${SurveyList},${Prompts},${NoTransfer})); 6 | catch h { // without a catch, dialplan stops execution on hangup !!! 7 | hang=1; 8 | return; 9 | } 10 | Set(RESULT=Invalid Exten); 11 | if ("${Exten}"="") return; 12 | 13 | Set(RESULT=Invalid SurveyList); 14 | if ("${SurveyList}"="") return; 15 | 16 | RESULT=OK; 17 | 18 | Set(LOCAL(SurveyStart)=${CUT(Prompts,:,1)}); 19 | Set(LOCAL(SurveyPrompt)=${CUT(Prompts,:,2)}); 20 | Set(LOCAL(SurveyThanks)=${CUT(Prompts,:,3)}); 21 | 22 | Set(__NO_TRANSFER=${NoTransfer}); 23 | Set(__NO_SLEEP=1); // Don't implement sleep in delayed Exten - return immediately to proceed survey 24 | Set(CHANNEL(language)=${DEFAULT_LANG}); 25 | 26 | if ("${SurveyStart}"!="") Playback(${SurveyStart}); 27 | 28 | Dial(LOCAL/${Exten}@iax/n,,rfg); // Call specified Exten 29 | 30 | if ("${DIALSTATUS}"="ANSWER") RESULT=OK; else Set(RESULT=${DIALSTATUS}); 31 | 32 | if ("${DIALSTATUS}"="ANSWER") { // Implement survey 33 | local num=; 34 | if ("${SurveyPrompt}"!="") Read(LOCAL(num),${SurveyPrompt},1,,,7); 35 | Set(num=${FILTER(1-5,${num})}); 36 | if ("${num}"!="") { // Write mark 37 | Set(ODBC_SURVEY(${SurveyList},${Exten},${CDR(uniqueid)})=${num}); 38 | if ("${SurveyThanks}"!="") Playback(${SurveyThanks}); 39 | } 40 | } 41 | 42 | return; 43 | } // macro Survey(Exten,SurveyList,Prompts,NoTransfer) 44 | -------------------------------------------------------------------------------- /PBX/ael/func/Switch.ael: -------------------------------------------------------------------------------- 1 | // Just call function "fn". Variables may be set as comma-separated list 2 | // insert into "Func" values ('090','ALL',NULL,'Switch','Exten(555,VOIX)','DialSuffix=/123'); 3 | macro Switch(fn,Var,Limit,FallBack) { 4 | NoOp(${leg1}>>${leg2} FUNCTION Switch(${fn},${Var},${Limit},${FallBack})); 5 | catch h { // without a catch, dialplan stops execution on hangup !!! 6 | hang=1; 7 | return; 8 | } 9 | 10 | Set(DATA=${DATA},FUNC=${CONTEXT};LIMIT=${Limit}); 11 | Set(CDR(x-data)={${DATA:1}}); 12 | 13 | if ("${Limit}"!="") { 14 | Set(LOCAL(RouteGroup)=${CUT(Limit,:,2)}); 15 | if ("${RouteGroup}"="") Set(LOCAL(RouteGroup)=${dnid}); 16 | 17 | Set(LOCAL(Limit)=${CUT(Limit,:,1)}); 18 | 19 | Set(GROUP(R)=${RouteGroup}); 20 | if (${GROUP_COUNT(${RouteGroup}@R)}>${Limit}) { 21 | if ("${FallBack}"="") { 22 | Congestion(); 23 | return; 24 | } else Set(LOCAL(fn)=${FallBack}); 25 | } 26 | } 27 | 28 | Set(RESULT=Invalid Func); 29 | if ("${fn}"="") return; 30 | 31 | RESULT=OK; 32 | 33 | for (LOCAL(i)=1; ${i}<=${FIELDQTY(Var,\,)}; LOCAL(i)=${i}+1) { 34 | Set(LOCAL(v)=${CUT(Var,\,,${i})}); 35 | Set(LOCAL(val)=${CUT(v,=,2)}); 36 | Set(${CUT(v,=,1)}=${EVAL(${val})}); 37 | } 38 | 39 | #include "ael/func/FUNC.INC" 40 | 41 | if ("${hang}"="1") Hangup(); 42 | return; 43 | } // macro Switch(fn,Var,Limit,FallBack) 44 | 45 | // Serialize up to 4 function calls 46 | // insert into "Func" values ('090','ALL',NULL,'Series','Exten(555,VOIX)','Exten(556,VOIX)'); 47 | macro Series(fn1,fn2,fn3,fn4) { 48 | NoOp(${leg1}>>${leg2} FUNCTION Series(${fn1},${fn2},${fn3},${fn4})); 49 | catch h { // without a catch, dialplan stops execution on hangup !!! 50 | hang=1; 51 | return; 52 | } 53 | Set(RESULT=Invalid Func); 54 | Set(fn=${fn1}); 55 | if ("${fn}"="") return; 56 | 57 | RESULT=OK; 58 | 59 | Set(DATA=${DATA},FUNC=${CONTEXT};FN=1); 60 | Set(CDR(x-data)={${DATA:1}}); 61 | 62 | #include "ael/func/FUNC.INC" 63 | 64 | Set(DATA=${DATA},FUNC=${CONTEXT};FN=2); 65 | Set(CDR(x-data)={${DATA:1}}); 66 | 67 | Set(fn=${fn2}); 68 | if ("${fn}"=""|"${hang}"="1"|"${RESULT}"="OK") return; 69 | #include "ael/func/FUNC.INC" 70 | 71 | Set(DATA=${DATA},FUNC=${CONTEXT};FN=3); 72 | Set(CDR(x-data)={${DATA:1}}); 73 | 74 | Set(fn=${fn3}); 75 | if ("${fn}"=""|"${hang}"="1"|"${RESULT}"="OK") return; 76 | #include "ael/func/FUNC.INC" 77 | 78 | Set(DATA=${DATA},FUNC=${CONTEXT};FN=4); 79 | Set(CDR(x-data)={${DATA:1}}); 80 | 81 | Set(fn=${fn4}); 82 | if ("${fn}"=""|"${hang}"="1"|"${RESULT}"="OK") return; 83 | #include "ael/func/FUNC.INC" 84 | 85 | return; 86 | } -------------------------------------------------------------------------------- /PBX/ael/func/TimeoutRoute.ael: -------------------------------------------------------------------------------- 1 | // Pass a call to the ${r1} route, then to ${r2} after timeout ${t} 2 | macro timeout_route(t,r1,r2) { 3 | NoOp(${leg1}>>${leg2} FUNCTION timeout_route(${t},${r1},${r2})); 4 | catch h { // without a catch, dialplan stops execution on hangup !!! 5 | hang=1; 6 | return; 7 | } 8 | Set(CHANNEL(language)=${DEFAULT_LANG}); 9 | Dial(LOCAL/${r1}@iax/n,${t},TtKkfg); 10 | Dial(LOCAL/${r2}@iax/n,,TtKkfg); 11 | return; 12 | } // macro timeout_route(t,r1,r2) 13 | -------------------------------------------------------------------------------- /PBX/ael/func/VoiceMail.ael: -------------------------------------------------------------------------------- 1 | // Mailbox interface. Does recording when (CALLERID(number) != cid), 2 | // else redirects to VoiceMailMain(${cid}@${MailContext}) 3 | macro VM(MailBox,MailContext,flags,fn) { 4 | NoOp(${leg1}>>${leg2} FUNCTION VoiceMail VM(${MailBox},${MailContext},${flags},${fn})); 5 | catch h { // without a catch, dialplan stops execution on hangup !!! 6 | hang=1; 7 | return; 8 | } 9 | Set(CHANNEL(language)=${DEFAULT_LANG}); 10 | 11 | if ("${MailContext}"="") { 12 | Set(MailContext=${BIND}); 13 | } 14 | 15 | if ("${MailBox}"="") { 16 | Set(MailBox=${cid}); 17 | } 18 | 19 | Set(DATA=${DATA},FUNC=${CONTEXT};BOX=${MailBox}@${MailContext}); 20 | Set(CDR(x-data)={${DATA:1}}); 21 | 22 | if (${MAILBOX_EXISTS(${MailBox}@${MailContext})}) { 23 | if ("${CALLERID(number)}"!="${MailBox}") { // Record message 24 | VoiceMail(${MailBox}@${MailContext},${flags}); 25 | } else { // Pass to mailbox 26 | VoiceMailMain(s${MailBox}@${MailContext}); 27 | } 28 | } else { // No mailbox attached 29 | Playback(vm-isunavail); 30 | } 31 | 32 | #include "ael/func/FUNC.INC" 33 | return; 34 | } // macro VM (MailBox,MailContext,flags,fn) 35 | -------------------------------------------------------------------------------- /PBX/ael/func/WorkDays.ael: -------------------------------------------------------------------------------- 1 | // Dial to "r1"|"r2", according to the number of simple calendar rules 2 | // Use trigger with z-level to bypass shedule 3 | // insert into "Func" values ('090','ALL','DialWorkDays','11:00-18:00^sat^*^*|10:00-19:00^mon-fri^*^*','531','585'); 4 | 5 | macro DialWorkDays(Spec,r1,r2,ZT) { 6 | NoOp(${leg1}>>${leg2} FUNCTION DialWorkDays(${Spec},${r1},${r2},${ZT})); 7 | catch h { // without a catch, dialplan stops execution on hangup !!! 8 | hang=1; 9 | return; 10 | } 11 | if ("${r2}"="") Set(r2=${r1}); 12 | 13 | Set(LOCAL(T)=${ODBC_VAR(${ZT})}); 14 | local num=${r2}; 15 | 16 | if ("${T}"="") { 17 | Set(LOCAL(qty)=${FIELDQTY(Spec,\|)}); 18 | for (LOCAL(i)=1; ${i}<=${qty}; LOCAL(i)=${i}+1) { 19 | Set(LOCAL(spc)=${CUT(Spec,\|,${i})}); 20 | 21 | Set(LOCAL(spc1)=${CUT(spc,^,1)}); 22 | Set(LOCAL(spc2)=${CUT(spc,^,2)}); 23 | Set(LOCAL(spc3)=${CUT(spc,^,3)}); 24 | Set(LOCAL(spc4)=${CUT(spc,^,4)}); 25 | 26 | Set(LOCAL(res)=${IFTIME(${spc1},${spc2},${spc3},${spc4}?1:0)}); 27 | if (${res}=1) { 28 | Set(LOCAL(num)=${r1}); 29 | break; 30 | } 31 | } 32 | } else { 33 | if ("${T}"="1") { 34 | Set(LOCAL(num)=${r1}); 35 | } 36 | } 37 | Set(CHANNEL(language)=${DEFAULT_LANG}); 38 | Dial(LOCAL/${num}@iax/n,,fg); 39 | 40 | return; 41 | } // macro DialWorkDays(Spec,r1,r2,ZT) 42 | 43 | // Call function "f1"|"f2", according to the number of simple calendar rules 44 | // Use trigger with z-level to bypass shedule 45 | // insert into "Func" values ('090','ALL',NULL,'WorkDays','09:00-21:00^*^*^*','_Dial(LOCAL/28584@iax/n|10|TtKkfgm)','_Dial(LOCAL/28585@iax/n|10|TtKkfgm)','ztWorkDays'); 46 | macro WorkDays(Spec,f1,f2,ZT) { 47 | NoOp(${leg1}>>${leg2} FUNCTION WorkDays(${Spec},${f1},${f2},${ZT})); 48 | catch h { // without a catch, dialplan stops execution on hangup !!! 49 | hang=1; 50 | return; 51 | } 52 | 53 | Set(RESULT=Invalid Spec); 54 | if ("${Spec}"="") return; 55 | Set(RESULT=Invalid Func); 56 | if ("${f1}"="") return; 57 | if ("${f2}"="") Set(f2=${f1}); 58 | 59 | RESULT=OK; 60 | 61 | Set(LOCAL(T)=${ODBC_VAR(${ZT})}); 62 | Set(LOCAL(fn)=${f2}); 63 | 64 | if ("${T}"="") { 65 | Set(LOCAL(qty)=${FIELDQTY(Spec,\|)}); 66 | for (LOCAL(i)=1; ${i}<=${qty}; LOCAL(i)=${i}+1) { 67 | Set(LOCAL(spc)=${CUT(Spec,\|,${i})}); 68 | 69 | Set(LOCAL(spc1)=${CUT(spc,^,1)}); 70 | Set(LOCAL(spc2)=${CUT(spc,^,2)}); 71 | Set(LOCAL(spc3)=${CUT(spc,^,3)}); 72 | Set(LOCAL(spc4)=${CUT(spc,^,4)}); 73 | 74 | Set(LOCAL(res)=${IFTIME(${spc1},${spc2},${spc3},${spc4}?1:0)}); 75 | if (${res}=1) { 76 | Set(LOCAL(fn)=${f1}); 77 | break; 78 | } 79 | } 80 | } else { 81 | if ("${T}"="1") { 82 | Set(LOCAL(fn)=${f1}); 83 | } 84 | } 85 | 86 | Set(DATA=${DATA},FUNC=${CONTEXT};ZT=${T};IFTIME=${res}); 87 | Set(CDR(x-data)={${DATA:1}}); 88 | 89 | #include "ael/func/FUNC.INC" 90 | return; 91 | } // macro WorkDays(Spec,f1,f2,ZT) 92 | -------------------------------------------------------------------------------- /PBX/ast/autodial.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # -w 3 | 4 | # Auto-redial behaviour for asterisk 5 | # Copyright (C) 2014 Dmitry Svyatogorov ds@vo-ix.ru 6 | 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU Affero General Public License as 9 | # published by the Free Software Foundation, either version 3 of the 10 | # License, or (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU Affero General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Affero General Public License 18 | # along with this program. If not, see . 19 | 20 | use strict; 21 | 22 | use IO::File; # File IO, part of core 23 | use Getopt::Std; # command line option processing 24 | use Fcntl qw(:DEFAULT :flock); # for file locking 25 | use POSIX qw(strftime); # pretty date formatting 26 | use Time::Local; # to do reverse of gmtime() 27 | use DBI; 28 | use File::Copy; 29 | 30 | #my $conn = Pg::connectdb("dbname=pbx"); 31 | # user=postgres 32 | my $conn; 33 | my @r; 34 | 35 | while (1==1) 36 | { 37 | $conn = DBI->connect("dbi:Pg:dbname=pbx","asterisk","",{PrintError => 0}); 38 | 39 | if ($DBI::err != 0) { 40 | print "ERR: Couldn't open connection: ".$DBI::errstr."\n"; 41 | sleep(15); 42 | next; 43 | } 44 | 45 | # Retrieve calls to try now 46 | my $sql = " SELECT * 47 | FROM \"AutoDial\" 48 | WHERE \"PlacedAt\" between (timestamp 'now' - interval '15 minutes') and (timestamp 'now' - interval '15 seconds') 49 | "; 50 | 51 | my $prep = $conn->prepare($sql); 52 | my $res = $prep->execute(); 53 | if (!defined $res) { 54 | print "Query failed: ".$conn->errstr."\n"; 55 | sleep(15); 56 | next; 57 | } 58 | 59 | while (@r = $prep->fetchrow_array()) 60 | { 61 | # my @row = $res->fetchrow; 62 | # print "$i\t\"$row[0]\"\t\"$row[1]\"\t\"$row[2]\"\n"; 63 | place_call(@r); 64 | } 65 | $conn->disconnect; 66 | sleep(15); 67 | } 68 | exit; 69 | ########################################################################## 70 | 71 | # Place Calls into Asterisk dir 72 | sub place_call 73 | { 74 | my $Src = shift; 75 | my $Dst = shift; 76 | my $PlacedAt = shift; 77 | my $LastCall = shift; 78 | my $NoAnswer = shift; 79 | my $Busy = shift; 80 | 81 | my $dir = "/var/tmp/autodial"; 82 | my $ast_dir = "/var/spool/asterisk/outgoing"; 83 | 84 | if (!(-e $dir)) 85 | { 86 | mkdir($dir, 0700) or die "Couldn't make directory: $!n"; 87 | } 88 | open(CALL, '+>', "$dir/autodial-$Src.call") or die "Couldn't open file for writing: $!n"; 89 | chmod(0600,"$dir/autodial-$Src.call") or die "Couldn't do chmod: $!n"; 90 | 91 | # Now, make a proper CallBack-file (look for Asterisk CallBacks) 92 | # print CALL "NRec = $NRec Exten = $Src\n"; 93 | print CALL "CallerID: $Src\n"; 94 | print CALL "Channel: LOCAL/$Dst\@autodial/n\n"; 95 | print CALL "WaitTime: 30\n"; 96 | print CALL "Context: autodial-dst\n"; 97 | print CALL "Extension: $Dst\n"; 98 | print CALL "Priority: 1\n"; 99 | print CALL "AlwaysDelete: yes\n"; 100 | print CALL "Archive: no\n"; 101 | 102 | close(CALL) or die "Couldn't close file: $!n"; 103 | rename("$dir/autodial-$Src.call","$ast_dir/autodial-$Src.call") or die "Couldn't move file: $!n"; 104 | rmdir($dir) or die "Couldn't remove directory: $!n"; 105 | } # sub place_call 106 | 107 | exit; 108 | ############################################################################# 109 | 110 | sub QUERY($) # Execute pgsql query, returning $res 111 | { 112 | my $query = shift; 113 | 114 | my $prep = $conn->prepare($query); 115 | my $res = $prep->execute(); 116 | if (!defined $res) { 117 | die "<$query>\n failed: ".$conn->errstr."\n"; 118 | } 119 | 120 | return $prep; 121 | } 122 | 123 | sub trim($) 124 | { 125 | my $string = shift; 126 | $string =~ s/^\s+//; 127 | $string =~ s/\s+$//; 128 | return $string; 129 | } 130 | # Left trim function to remove leading whitespace 131 | sub ltrim($) 132 | { 133 | my $string = shift; 134 | $string =~ s/^\s+//; 135 | return $string; 136 | } 137 | # Right trim function to remove trailing whitespace 138 | sub rtrim($) 139 | { 140 | my $string = shift; 141 | $string =~ s/\s+$//; 142 | return $string; 143 | } 144 | 145 | sub ulc($) # UTF-8 to lower_case 146 | { 147 | my $string = shift; 148 | return encode_utf8(lc(decode_utf8($string))); 149 | } 150 | 151 | sub uuc($) # UTF-8 to UPPER_case 152 | { 153 | my $string = shift; 154 | return encode_utf8(uc(decode_utf8($string))); 155 | } 156 | -------------------------------------------------------------------------------- /PBX/ast/call.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # -w 3 | # Place DialOut calls into Asterisk dir 4 | 5 | use strict; 6 | use IO::File; # File IO, part of core 7 | 8 | place_call ($ARGV[0],$ARGV[1],$ARGV[2],$ARGV[3]); 9 | exit; 10 | 11 | sub place_call 12 | { 13 | my $Src = shift; 14 | my $Dst = shift; 15 | my $BIND = shift; 16 | my $UUID = shift; 17 | 18 | my $dir = "/var/tmp/dialout"; 19 | # my $ast_dir = "/var/tmp/dialout"; 20 | my $ast_dir = "/var/spool/asterisk/outgoing"; 21 | 22 | if (!(-e $dir)) 23 | { 24 | mkdir($dir, 0700) or die "Couldn't make directory: $!n"; 25 | } 26 | open(CALL, '+>', "$dir/dialout-$Src.call") or die "Couldn't open file for writing: $!n"; 27 | chmod(0600,"$dir/dialout-$Src.call") or die "Couldn't do chmod: $!n"; 28 | 29 | # Now, make a proper CallBack-file (look for Asterisk CallBacks) 30 | # print CALL "NRec = $NRec Exten = $Src\n"; 31 | print CALL "CallerID: $Src\n"; 32 | print CALL "Channel: LOCAL/$Dst\@dialout/n\n"; 33 | print CALL "WaitTime: 30\n"; 34 | print CALL "Context: dialout-dst\n"; 35 | print CALL "Extension: $Dst\n"; 36 | print CALL "Priority: 1\n"; 37 | print CALL "AlwaysDelete: yes\n"; 38 | print CALL "Archive: no\n"; 39 | print CALL "Set: __BIND=$BIND\n" if ($BIND); 40 | print CALL "Set: __SRC=$Src\n"; 41 | print CALL "Set: __DST=$Dst\n"; 42 | print CALL "Set: __UUID=$UUID\n"; 43 | 44 | close(CALL) or die "Couldn't close file: $!n"; 45 | rename("$dir/dialout-$Src.call","$ast_dir/dialout-$Src.call") or die "Couldn't move file: $!n"; 46 | rmdir($dir) or die "Couldn't remove directory: $!n"; 47 | } # sub place_call 48 | -------------------------------------------------------------------------------- /PBX/ast/contents.txt: -------------------------------------------------------------------------------- 1 | Some valuable scripts for asterisk PBX 2 | must be placed here. -------------------------------------------------------------------------------- /PBX/ast/devstate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #chan=`cat /root/chan.txt | awk '{ if ($3 ~ "^(O|I|B)$") print $2 }'` 3 | 4 | while : 5 | do 6 | date 7 | dev=`asterisk -rx 'devstate list' | grep -v 'NOT_INUSE' | grep 'Custom:' | grep -v '\*' | grep -v '#' | cut -d ':' -f 3 | cut -d "'" -f 1` 8 | chan=`asterisk -rx 'group show channels' | awk '{ if ($3 ~ "^(O|I|B|C)$") print $2 }'` 9 | for d in ${dev}; do 10 | echo "${chan}" | grep "${d}" 11 | if [ $? -ne 0 ]; then 12 | asterisk -rx "devstate change Custom:${d} NOT_INUSE" 13 | d=`echo -n ${d} | sed 's/+/*/'` 14 | asterisk -rx "devstate change Custom:${d} NOT_INUSE" 15 | fi 16 | done 17 | 18 | dev=`asterisk -rx 'devstate list' | grep -v 'NOT_INUSE' | grep 'Custom:' | grep 'Q\*' | cut -d ':' -f 3 | cut -d "'" -f 1` 19 | chan=`asterisk -rx 'group show channels' | awk '{ if ($3 ~ "^(O|I|B|C)$") print $2 }'` 20 | for d in ${dev}; do 21 | dd=`echo -n $d | cut -d '*' -f 2` 22 | echo "dd=${dd}" 23 | echo "${chan}" | grep "${dd}" 24 | if [ $? -ne 0 ]; then 25 | asterisk -rx "devstate change Custom:${d} NOT_INUSE" 26 | fi 27 | done 28 | 29 | for (( i=0; i<3; i++ )) 30 | do 31 | sleep 1 32 | done 33 | done 34 | -------------------------------------------------------------------------------- /PBX/ast/schema/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ds-voix/VX-PBX/75e3292dbc21876405258cb60037853137727a83/PBX/ast/schema/__init__.py -------------------------------------------------------------------------------- /PBX/ast/schema/sip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -u 2 | # -*- coding: utf-8 -*- 3 | import cgi 4 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer 5 | #import subprocess 6 | from subprocess import Popen, PIPE 7 | import sys 8 | import syslog 9 | from socket import * 10 | 11 | import psycopg2 12 | import psycopg2.extras 13 | from psycopg2.extensions import adapt 14 | 15 | HOST = '127.0.0.1' 16 | PORT = 5038 17 | USER = 'clear_sip' 18 | PASS = 'sip_clear' 19 | 20 | def SipPeers(): 21 | try: 22 | # Login to AMI 23 | ast = socket(AF_INET, SOCK_STREAM) 24 | # ast.settimeout(1) 25 | ast.connect((HOST, PORT)) 26 | data = "" 27 | while "\r\n" not in data: 28 | data += ast.recv(1500) 29 | #print repr(data) 30 | 31 | params = ["Action: login", 32 | "Events: off", 33 | "Username: %s" % USER, 34 | "Secret: %s" % PASS] 35 | 36 | ast.send("\r\n".join(params) + "\r\n\r\n") 37 | # receive login response 38 | data = "" 39 | while "\r\n\r\n" not in data: 40 | data += ast.recv(1024) 41 | # print data 42 | data = "" 43 | 44 | params = ["Action: SIPpeers", 45 | # "Peer:: %s" % Name, 46 | "ActionID: 001"] 47 | 48 | ast.send("\r\n".join(params) + "\r\n\r\n") 49 | # receive answer 50 | while "PeerlistComplete" not in data: 51 | data += ast.recv(1024) 52 | # print data 53 | # data = "" 54 | 55 | ast.send("Action: Logoff\r\n\r\n") 56 | ast.close() 57 | except error, E: 58 | # syslog.syslog("AMI ERROR: " + str(E)) 59 | return "ERROR AMI: %s" % (str(E)) 60 | 61 | # syslog.syslog("OK QueueStatus: %s" % Queue) 62 | return data 63 | 64 | def SipPeer(Name): 65 | try: 66 | # Login to AMI 67 | ast = socket(AF_INET, SOCK_STREAM) 68 | # ast.settimeout(1) 69 | ast.connect((HOST, PORT)) 70 | data = "" 71 | while "\r\n" not in data: 72 | data += ast.recv(1500) 73 | #print repr(data) 74 | 75 | params = ["Action: login", 76 | "Events: off", 77 | "Username: %s" % USER, 78 | "Secret: %s" % PASS] 79 | 80 | ast.send("\r\n".join(params) + "\r\n\r\n") 81 | # receive login response 82 | data = "" 83 | while "\r\n\r\n" not in data: 84 | data += ast.recv(1024) 85 | # print data 86 | data = "" 87 | 88 | params = ["Action: SIPshowpeer", 89 | "Peer: %s" % Name, 90 | "ActionID: 001"] 91 | 92 | ast.send("\r\n".join(params) + "\r\n\r\n") 93 | # receive answer 94 | while "\r\n\r\n" not in data: 95 | data += ast.recv(1024) 96 | # print data 97 | # data = "" 98 | 99 | ast.send("Action: Logoff\r\n\r\n") 100 | ast.close() 101 | except error, E: 102 | # syslog.syslog("AMI ERROR: " + str(E)) 103 | return "ERROR AMI: %s" % (str(E)) 104 | 105 | # syslog.syslog("OK QueueStatus: %s" % Queue) 106 | return data 107 | 108 | def SipList(BIND): 109 | try: 110 | connect = psycopg2.connect("dbname='pbx' user='postgres' password=''") 111 | except: 112 | return ['', 'SQL ERROR: Can not connect to database', ''] 113 | 114 | cur = connect.cursor() #(cursor_factory=psycopg2.extras.DictCursor) 115 | 116 | sql = """select name, fullcontact from sip 117 | where ("BIND" = %s) order by name;""" % adapt(BIND) 118 | 119 | # print sql 120 | 121 | res = [] 122 | body = '' 123 | try: 124 | cur.execute(sql) 125 | if (cur.rowcount > 0): 126 | res.append(cur.rowcount) 127 | body = 'name;fullcontact;status\n' 128 | for row in cur.fetchall(): 129 | data = SipPeer(row[0]).splitlines() 130 | status = '' 131 | for l in data: 132 | if (l[:7] == 'Status:'): 133 | status = l[8:] 134 | break 135 | body += "%s;%s;%s\n" % (row[0], row[1], status) 136 | else: 137 | res = [0, 'SIP accounts not found'] 138 | except psycopg2.Error, e: 139 | res = ['', "SQL ERROR: " + str(e.pgerror)] 140 | 141 | cur.close() 142 | return(res, body) 143 | 144 | 145 | def SipLog(BIND, sip): 146 | # 147 | pipe = Popen("""/usr/local/sbin/sip-log '%s+%s'""" % (BIND, sip), shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE) 148 | 149 | stdout, stderr = pipe.communicate() 150 | result = pipe.returncode 151 | 152 | pipe.stdin.close() 153 | pipe.wait() 154 | 155 | return [ "%s" % result, stdout ] 156 | -------------------------------------------------------------------------------- /PBX/sbin/AntiTheft/LastSeen: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | umask 133 3 | 4 | HOST=`/bin/hostname` 5 | FQDN=`/bin/hostname -f` 6 | SYSADMIN=ds@vo-ix.ru 7 | 8 | LOG="${1:-/var/log/asterisk/secure}" 9 | [ -s ${LOG} ] || exit 0 10 | 11 | STATE="/var/tmp/syslog.`echo ${LOG} | /usr/bin/md5sum | /bin/cut -d" " -f1`.offset" 12 | 13 | OFFSET=`/bin/cat ${STATE} 2>/dev/null | /usr/bin/tr -cd '[[:digit:]]'` 14 | OFFSET=${OFFSET:-0} 15 | TAIL=`/usr/bin/stat -c %s "${LOG}"` 16 | [ ${TAIL} -eq ${OFFSET} ] && exit 0 17 | echo ${TAIL} > "${STATE}" 18 | 19 | # Log was rewritten 20 | [ ${TAIL} -lt ${OFFSET} ] && OFFSET=0 21 | 22 | # Async workaround 23 | [ ${OFFSET} -ge 256 ] && OFFSET=$(( ${OFFSET} - 256 )) || OFFSET=0 24 | 25 | # Prevent slowdown on huge logs - skip more then 64MB blocks 26 | #if [ $(( ${TAIL} - ${OFFSET} )) -gt 65536000 ]; then 27 | # OFFSET=$(( ${TAIL} - 65536000 )) 28 | # echo SKIP 29 | #fi 30 | 31 | CHUNK="`/bin/dd if=\"${LOG}\" ibs=1 skip=${OFFSET} 2>/dev/null | /bin/grep 'SuccessfulAuth' | /bin/cut -d ',' -f 6,9 | /bin/cut -d '\"' -f 2,4 | /bin/sed -r 's/\"IPV4\/UDP\//\t/ ; s/\/.+\$//'`" 32 | 33 | if [ ! -z "${CHUNK}" ]; then 34 | TUPLES=`(/bin/cat < "@@LastSeen"."DT") 46 | LEFT OUTER JOIN sip ON (sip.name = "LastSeen"."Name") 47 | WHERE "LastSeen"."IP" != "@@LastSeen"."IP" 48 | ORDER BY "LastSeen"."Name", "LastSeen"."DT" DESC; 49 | 50 | 51 | COMMIT TRANSACTION; 52 | EOF 53 | ) | /usr/bin/psql -U postgres -qt pbx` 54 | 55 | OIFS=$IFS 56 | IFS=$'\n' 57 | T=($TUPLES) 58 | 59 | IFS='|' 60 | NOTE='' 61 | 62 | for t in "${T[@]}" 63 | do 64 | f=($t) 65 | dt=`echo ${f[0]} | /bin/sed -r 's/^[ ]+// ; s/\.[0-9]+\+/+/'` 66 | name=`echo ${f[1]} | /bin/sed 's/ //g'` 67 | ip1=`echo ${f[2]} | /bin/sed 's/ //g'` 68 | code1=`echo ${f[3]} | /bin/sed 's/ //g'` 69 | ip0=`echo ${f[4]} | /bin/sed 's/ //g'` 70 | code0=`echo ${f[5]} | /bin/sed 's/ //g'` 71 | insecure=`echo ${f[6]} | /bin/sed 's/ //g'` 72 | 73 | if [ "${code0}" != "${code1}" ]; then 74 | if [ "${code1}" != "RU" ] && [ "${code1}" != "" ] && [ "${insecure}" != "t" ]; then 75 | acc=`echo ${name} | /bin/sed -r 's/\+.*$//'` 76 | echo "$dt !!!ACHTUNG!!! account=${acc} name=${name} last_seen=${ip0}@${code0} code moved out of @RU!" 77 | # Shutdown egress immediately! 78 | /usr/local/sbin/account ${acc} down out 79 | ACHTUNG=1 80 | NOTE="$dt !!!ACHTUNG!!! account=${acc} name=${name} last_seen=${ip0}@${code0} moved to ${ip1}@${code1}! 81 | $NOTE" 82 | fi 83 | fi 84 | echo "${dt} ${name} ${ip0} >> $ip1 @${code1}" 85 | done 86 | 87 | if [ "${ACHTUNG}" == "1" ]; then 88 | ACHTUNG="/var/tmp/account.${acc}.down" 89 | if [ ! -f ${ACHTUNG} ]; then 90 | echo ${NOTE} > ${ACHTUNG} 91 | echo "${dt} Restarting PBX because of ACHTUNG!" 92 | /sbin/service asterisk restart 93 | fi 94 | 95 | echo ${NOTE} | /usr/local/sbin/mailer -f "${HOST} " -t "PBX maintainers <${SYSADMIN}>" -s "!!!ACHTUNG!!! Code moved out of @RU!" 96 | fi 97 | fi 98 | -------------------------------------------------------------------------------- /PBX/sbin/AntiTheft/sip-watch: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | umask 177 3 | 4 | TIMEOUT=60 5 | 6 | log="/bin/logger -s -p local7.notice -t sip-watch@AntiTheft -- " 7 | 8 | _exit () { 9 | eval ${log} '\*\* BRAKE' 10 | exit 0 11 | } 12 | 13 | trap "_exit" SIGINT SIGTERM 14 | 15 | eval ${log} '\*\* STARTED. watch sip every ${TIMEOUT}s' 16 | while : 17 | do 18 | /usr/local/sbin/AntiTheft/LastSeen >> /var/log/sip-watch.log 2>&1 19 | 20 | for (( i=0; i<${TIMEOUT}; i++ )) 21 | do 22 | sleep 1 23 | done 24 | done 25 | -------------------------------------------------------------------------------- /PBX/sbin/account: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | umask 177 3 | 4 | # account NAME [up|down] [in|out|] 5 | 6 | BIND=$1 7 | if [ "${BIND}" == "" ]; then 8 | echo "Usage: $0 BIND [up|down] [in|out|]" 9 | exit 10 | fi 11 | 12 | if [ "$2" == "" ]; then 13 | # Check current status 14 | r=`psql -U postgres -qtc "SELECT \"NRec\",\"Channel\" from \"Route\" where (\"BIND\" = '$BIND' and \"NRec\" < 0) order by \"NRec\" desc;" pbx | sed -r 's/[\s]+/ /g;s/[|]/ >> channel #/'` 15 | if [ "${r}" != "" ]; then 16 | echo "EGRESS DOWN:" 17 | echo "${r}" 18 | else 19 | r=`psql -U postgres -qtc "SELECT \"NRec\",\"Channel\",\"Description\" from \"Route\" where (\"BIND\" = '$BIND') order by \"NRec\" desc;" pbx | sed -r 's/[ ]+/ /g;s/[|]/ >> channel #/'` 20 | if [ "${r}" != "" ]; then 21 | echo "EGRESS UP:" 22 | echo "${r}" 23 | else 24 | echo "EGRESS NOT SET: No routes for \"${BIND}\"" 25 | fi 26 | fi 27 | 28 | p=`psql -U postgres -qtc "SELECT \"Exten\",\"Description\" from \"Func\" where \"BIND\" = '_' and \"Exten\" in (SELECT \"Exten\" from \"Func\" where \"BIND\" = '$BIND' and \"Exten\" ~ '^[0-9]{10}$');" pbx | sed -r 's/[\s]+/ /g'` 29 | if [ "${p}" != "" ]; then 30 | echo '' 31 | echo "INGRESS DOWN:" 32 | echo "${p}" 33 | else 34 | echo '' 35 | fi 36 | 37 | r=`psql -U postgres -qtc "SELECT \"Exten\",\"Description\" from \"Func\" where \"BIND\" = '${BIND}' and \"Exten\" ~ '^[0-9]{10}$' and NOT \"Exten\" in (SELECT \"Exten\" from \"Func\" where \"BIND\" = '_');" pbx | sed -r 's/[\s]+/ /g'` 38 | if [ "${r}" != "" ]; then 39 | echo "INGRESS UP:" 40 | echo "${r}" 41 | fi 42 | 43 | if [ "${p}${r}" == "" ]; then 44 | echo "INGRESS NOT SET: No one DID for \"${BIND}\"" 45 | fi 46 | 47 | exit 48 | fi 49 | 50 | [ "${BIND}" == "REDIRECT" ] && exit 51 | 52 | [ "$2" == "down" ] || del='-- ' 53 | [ "${del}" == "" ] && d='DOWN' || d='UP' 54 | 55 | if [ "$3" != "out" ]; then 56 | p=`psql -U postgres -qtc "SELECT array_to_string( ARRAY(SELECT \"Exten\" from \"Func\" where \"BIND\" = '$BIND' and \"Exten\" ~ '^[0-9]{10}$'), ',');" pbx | egrep -o '[0-9,]+'` 57 | if [ $? -gt 0 ]; then 58 | echo "No DID found for \"$BIND\"" 59 | exit 1 60 | fi 61 | 62 | ( 63 | echo "${del}[Func ${p}] ; `basename $0` \"${BIND}\"" 64 | echo " Macro = Indication ; (fn,Timeout,NoTransfer,Limit)" 65 | echo " P1 = NonExistent" 66 | echo " P2 = 86400" 67 | ) | /usr/local/sbin/upsert -b '_' -IDT | psql -U postgres -q pbx 68 | 69 | echo "INGRESS ${d} for DID [$p]" 70 | fi 71 | 72 | if [ "$3" != "in" ]; then 73 | r=`psql -U postgres -qtc "SELECT \"NRec\" from \"Route\" where \"BIND\" = '$BIND' order by \"NRec\" desc LIMIT 1;" pbx | egrep -o '[0-9]+'` 74 | 75 | if [ $? -gt 0 ]; then 76 | echo "No Route found for \"$BIND\"" 77 | r=`psql -U postgres -qtc "SELECT \"Exten\" from \"Func\" where (\"BIND\" = '$BIND' and \"Exten\" ~ '^[0-9]{10}$') order by \"Exten\" LIMIT 1;" pbx | egrep -o '[0-9]+'` 78 | if [ $? -eq 0 ]; then 79 | echo "Using DID-BASED \"-${r}\"" 80 | else 81 | echo "No DID found for \"$BIND\"" 82 | exit 1 83 | fi 84 | fi 85 | 86 | ( 87 | echo "${del}[Route -${r}] ; `basename $0` \"${BIND}\"" 88 | echo " Order = -1" 89 | echo " Channel = 666 ; LOCAL" 90 | echo " Mangle = 666 ; CONGESTION" 91 | echo " Level = 0" 92 | echo " Insecure = 1" 93 | echo " NoMore = 1" 94 | ) | /usr/local/sbin/upsert -b "${BIND}" -IDT | psql -U postgres -q pbx 95 | 96 | echo "EGRESS ${d} at [Route -${r}]" 97 | fi 98 | -------------------------------------------------------------------------------- /PBX/sbin/autodial: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # -w 3 | 4 | # Auto-redial behaviour for asterisk 5 | # Copyright (C) 2014 Dmitry Svyatogorov ds@vo-ix.ru 6 | 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU Affero General Public License as 9 | # published by the Free Software Foundation, either version 3 of the 10 | # License, or (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU Affero General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Affero General Public License 18 | # along with this program. If not, see . 19 | 20 | use strict; 21 | 22 | use IO::File; # File IO, part of core 23 | use Getopt::Std; # command line option processing 24 | use Fcntl qw(:DEFAULT :flock); # for file locking 25 | use POSIX qw(strftime); # pretty date formatting 26 | use Time::Local; # to do reverse of gmtime() 27 | use DBI; 28 | use File::Copy; 29 | 30 | #my $conn = Pg::connectdb("dbname=pbx"); 31 | # user=postgres 32 | my $conn; 33 | my @r; 34 | 35 | while (1==1) 36 | { 37 | $conn = DBI->connect("dbi:Pg:dbname=pbx","asterisk","",{PrintError => 0}); 38 | 39 | if ($DBI::err != 0) { 40 | print "ERR: Couldn't open connection: ".$DBI::errstr."\n"; 41 | sleep(15); 42 | next; 43 | } 44 | 45 | # Retrieve calls to try now 46 | my $sql = " SELECT * 47 | FROM \"AutoDial\" 48 | WHERE \"PlacedAt\" between (timestamp 'now' - interval '15 minutes') and (timestamp 'now' - interval '15 seconds') 49 | "; 50 | 51 | my $prep = $conn->prepare($sql); 52 | my $res = $prep->execute(); 53 | if (!defined $res) { 54 | print "Query failed: ".$conn->errstr."\n"; 55 | sleep(15); 56 | next; 57 | } 58 | 59 | while (@r = $prep->fetchrow_array()) 60 | { 61 | # my @row = $res->fetchrow; 62 | # print "$i\t\"$row[0]\"\t\"$row[1]\"\t\"$row[2]\"\n"; 63 | place_call(@r); 64 | } 65 | $conn->disconnect; 66 | sleep(15); 67 | } 68 | exit; 69 | ########################################################################## 70 | 71 | # Place Calls into Asterisk dir 72 | sub place_call 73 | { 74 | my $Src = shift; 75 | my $Dst = shift; 76 | my $PlacedAt = shift; 77 | my $LastCall = shift; 78 | my $NoAnswer = shift; 79 | my $Busy = shift; 80 | 81 | my $dir = "/var/tmp/autodial"; 82 | my $ast_dir = "/var/spool/asterisk/outgoing"; 83 | 84 | if (!(-e $dir)) 85 | { 86 | mkdir($dir, 0700) or die "Couldn't make directory: $!n"; 87 | } 88 | open(CALL, '+>', "$dir/autodial-$Src.call") or die "Couldn't open file for writing: $!n"; 89 | chmod(0600,"$dir/autodial-$Src.call") or die "Couldn't do chmod: $!n"; 90 | 91 | # Now, make a proper CallBack-file (look for Asterisk CallBacks) 92 | # print CALL "NRec = $NRec Exten = $Src\n"; 93 | print CALL "CallerID: $Src\n"; 94 | print CALL "Channel: LOCAL/$Dst\@autodial/n\n"; 95 | print CALL "WaitTime: 30\n"; 96 | print CALL "Context: autodial-dst\n"; 97 | print CALL "Extension: $Dst\n"; 98 | print CALL "Priority: 1\n"; 99 | print CALL "AlwaysDelete: yes\n"; 100 | print CALL "Archive: no\n"; 101 | 102 | close(CALL) or die "Couldn't close file: $!n"; 103 | rename("$dir/autodial-$Src.call","$ast_dir/autodial-$Src.call") or die "Couldn't move file: $!n"; 104 | rmdir($dir) or die "Couldn't remove directory: $!n"; 105 | } # sub place_call 106 | 107 | exit; 108 | ############################################################################# 109 | 110 | sub QUERY($) # Execute pgsql query, returning $res 111 | { 112 | my $query = shift; 113 | 114 | my $prep = $conn->prepare($query); 115 | my $res = $prep->execute(); 116 | if (!defined $res) { 117 | die "<$query>\n failed: ".$conn->errstr."\n"; 118 | } 119 | 120 | return $prep; 121 | } 122 | 123 | sub trim($) 124 | { 125 | my $string = shift; 126 | $string =~ s/^\s+//; 127 | $string =~ s/\s+$//; 128 | return $string; 129 | } 130 | # Left trim function to remove leading whitespace 131 | sub ltrim($) 132 | { 133 | my $string = shift; 134 | $string =~ s/^\s+//; 135 | return $string; 136 | } 137 | # Right trim function to remove trailing whitespace 138 | sub rtrim($) 139 | { 140 | my $string = shift; 141 | $string =~ s/\s+$//; 142 | return $string; 143 | } 144 | 145 | sub ulc($) # UTF-8 to lower_case 146 | { 147 | my $string = shift; 148 | return encode_utf8(lc(decode_utf8($string))); 149 | } 150 | 151 | sub uuc($) # UTF-8 to UPPER_case 152 | { 153 | my $string = shift; 154 | return encode_utf8(uc(decode_utf8($string))); 155 | } 156 | -------------------------------------------------------------------------------- /PBX/sbin/calls: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | filter="$1" 3 | calls=`asterisk -rx 'core show channels verbose'` 4 | 5 | echo "======================== EGRESS: ===========================================" 6 | echo 'Extension State Data CallerID Duration' 7 | echo "${calls}" | egrep 'RouteCall' | grep -v AppDial | grep "${filter}" | awk '{printf("%-16s %-7s %-26s %13s %10s\n", $3,$5,$7,$8,$9)}' 8 | echo " TOTAL =" `echo "${calls}" | egrep 'RouteCall' | grep -v AppDial | grep "${filter}" | wc -l` 9 | 10 | echo "" 11 | echo "======================== INGRESS: ======================================" 12 | echo 'Application State Data CallerID Duration' 13 | echo "${calls}" | egrep '^SIP/A0' | grep -v Outgoing | grep "${filter}" | awk '{printf("%-12s %-7s %-26s %13s %10s\n", $6,$5,$7,$8,$9)}' 14 | echo " TOTAL =" `echo "${calls}" | egrep '^SIP/A0' | grep -v Outgoing | grep "${filter}" | wc -l` 15 | #5 State 16 | #6 Application 17 | #7 Data 18 | 19 | echo "" 20 | echo "======================== EXTEN: =================" 21 | echo 'SIP-peer Extension State Duration' 22 | echo "${calls}" | egrep '^SIP/' | egrep 'sip|exten' | sed -r 's/^SIP\///' | sed -r 's/-.+exten// ; s/-.+sip//' | grep "${filter}" | awk '{printf("%-16s %-15s %-6s %9s\n", $1,$2,$4,$9)}' 23 | echo " TOTAL =" `echo "${calls}" | egrep '^SIP/' | egrep 'sip|exten' | grep "${filter}" | wc -l` 24 | #1 SIP-peer 25 | #2 Extension 26 | #4 State 27 | #8 Duration -------------------------------------------------------------------------------- /PBX/sbin/card: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | umask 122 3 | 4 | CONF="$1" 5 | 6 | if [ "`dirname ${CONF}`" == "." ]; then 7 | if [ `echo "${CONF}" | egrep -i '\.(conf|cfg|ini)$'` ] || [ `echo "${CONF}" | wc -m` -lt 7 ]; then 8 | CONF=`find /pbx/conf/ -type f -name "${CONF}*"` 9 | count=`echo "${CONF}" | wc -l` 10 | if [ ${count} -gt 1 ]; then 11 | echo " Ambiguous \"$1\":" 12 | echo "${CONF}" 13 | exit 0 14 | fi 15 | 16 | [ "${CONF}" == "" ] && CONF="/pbx/conf/$1" 17 | [ `echo "${CONF}" | egrep -i '\.(conf|cfg|ini)$'` ] || CONF="${CONF}.conf" 18 | else # Try to search inside content 19 | count=`grep -Rli "${CONF}" /pbx/conf/ | wc -l` 20 | [ ${count} -eq 0 ] && echo "Not found \"${CONF}\" inside configurations" && exit 0 21 | if [ ${count} -gt 1 ]; then 22 | echo " Multiple \"${CONF}\" inside configurations:" 23 | grep -Rli "${CONF}" /pbx/conf/ 24 | exit 0 25 | fi 26 | CONF="`grep -Rli "${CONF}" /pbx/conf/`" 27 | fi 28 | fi 29 | 30 | mt=`stat -c '%Y' "${CONF}" 2>/dev/null || echo 0` 31 | f=`readlink -f "${CONF}" | egrep '^/pbx/conf/'` 32 | [ $? -ne 0 ] && exit 33 | 34 | echo `date "+%Y.%m.%d %H:%M"`"${SUDO_UID} ${SUDO_USER}: OPEN ${f}" >> /var/log/card.log 35 | 36 | if [ "$2" != "!" ] && [ "$2" != "!!" ]; then 37 | nano -kiw -Y "/usr/share/nano/conf.nanorc" -o `dirname ${f}` "${CONF}" 38 | mt1=`stat -c '%Y' "${CONF}" 2>/dev/null || echo 0` 39 | 40 | if [ "$mt1" -eq "$mt" ]; then 41 | echo 'Config unchanged' 42 | exit 0 43 | fi 44 | fi 45 | 46 | f=`readlink -f "${CONF}"` 47 | sql=`dirname ${f}` 48 | 49 | if [ "`dirname ${f}`" == "/pbx/conf" ]; then 50 | BIND='.conf' 51 | sql=/pbx/sql/`basename $sql`'+'`basename $f conf`'sql' 52 | else 53 | BIND='.dir' 54 | if [ "`dirname ${f}`" == "/pbx/conf/REDIRECT" ]; then 55 | sql=/pbx/sql/`basename $sql`'+'`basename $f conf`'sql' 56 | else 57 | CONF="`dirname ${f}`" 58 | sql=/pbx/sql/`basename $sql`'.sql' 59 | fi 60 | fi 61 | 62 | [ "${SUDO_USER}" == "" ] && [ "${USER}" == "root" ] && SU=' -S' || SU='' 63 | 64 | # Force delete-insert for all objects 65 | if [ "$3" == "!" ] || [ "$2" == "!!" ]; then 66 | DI=' -DI' || DI='' 67 | fi 68 | 69 | /usr/local/sbin/upsert -c "${CONF}" -b "$BIND" -TC${SU}${DI} > "${sql}" 70 | res=$? 71 | 72 | if [ ${res} -eq 0 ]; then 73 | /usr/local/sbin/conf "${sql}" 74 | res=$? 75 | else 76 | echo `date "+%Y.%m.%d %H:%M"`"${SUDO_UID} ${SUDO_USER}: ERROR [${res}] in config ${f}" >> /var/log/card.log 77 | exit 78 | fi 79 | 80 | if [ ${res} -eq 0 ]; then 81 | echo `date "+%Y.%m.%d %H:%M"`"${SUDO_UID} ${SUDO_USER}: OK ${f}" >> /var/log/card.log 82 | else 83 | echo `date "+%Y.%m.%d %H:%M"`"${SUDO_UID} ${SUDO_USER}: ERROR [${res}] in SQL ${sql}" >> /var/log/card.log 84 | fi 85 | -------------------------------------------------------------------------------- /PBX/sbin/cdr: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | umask 177 3 | 4 | if [ "$1" == "" ]; then 5 | echo " Usage: $0 [phone_regex]" 6 | exit 7 | fi 8 | 9 | BIND=$1 10 | if (echo $BIND | egrep -q '^[0-9]+\.[0-9]+$'); then 11 | BIND="linkedid='${BIND}'" 12 | FIELDS='*' 13 | ORDER='calldate desc, uniqueid desc'; 14 | else 15 | BIND="\"x-domain\"='${BIND}'" 16 | FIELDS='calldate,clid,duration,billsec,accountcode,linkedid,"x-tag","x-spec"||"x-dialed","x-result","x-data"' 17 | ORDER='calldate desc'; 18 | fi 19 | 20 | [ "$2" != "" ] && ACCOUNT=" and accountcode ~ '$2'" 21 | 22 | cat < now() - interval '1 week' 27 | order by ${ORDER} LIMIT 10000; 28 | EOF 29 | -------------------------------------------------------------------------------- /PBX/sbin/cdr.rs232: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -u 2 | # -*- coding: utf-8 -*- 3 | 4 | # Read line-by line serial input from /dev/ttyS0 5 | # Then store it in proper named log 6 | # (c) 2010.12.20 by PnD! 7 | # upd. (c) 2014.09.10 by PnD! 8 | # License: GPLv3 9 | 10 | import sys 11 | import subprocess 12 | import re 13 | import time 14 | import serial 15 | 16 | if (len(sys.argv) < 2): 17 | path = '/var/log/' 18 | else: 19 | path = sys.argv[1].rstrip('/') + '/' 20 | 21 | fname = path + time.strftime('%Y%m%d.log', time.localtime() ) 22 | 23 | f = open(fname, 'a') 24 | #e = open(path + 'fonex.err', 'a') 25 | 26 | com = serial.Serial('/dev/ttyS0', 9600, timeout=1) 27 | 28 | print time.strftime('%d.%m.%Y %H:%M:%S', time.localtime() ) + ': Logger started' 29 | active = True 30 | 31 | while True: 32 | while com.isOpen(): 33 | line = com.readline() 34 | if (re.search('^\d\d:\d\d', line) != None): 35 | f.close() 36 | fname = path + time.strftime('%Y%m%d.log', time.localtime() ) 37 | f = open(fname, 'a') 38 | if (line != ''): 39 | f.write(line) 40 | f.flush() 41 | # print line.rstrip(' \r\n') 42 | 43 | if active: 44 | print time.strftime('%d.%m.%Y %H:%M:%S', time.localtime() ) + ': Serial is DOWN' 45 | active = False 46 | 47 | time.sleep(1) 48 | com.open() 49 | 50 | if com.isOpen(): 51 | print time.strftime('%d.%m.%Y %H:%M:%S', time.localtime() ) + ': Serial is UP' 52 | active = True 53 | 54 | sys.exit() 55 | -------------------------------------------------------------------------------- /PBX/sbin/cdr.tcp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python -u 2 | # -*- coding: utf-8 -*- 3 | 4 | # Read line-by line input from tcp connection 5 | # Then store it in proper named log 6 | # (c) 2014.10.24 by PnD! 7 | # License: GPLv3 8 | 9 | import sys 10 | import subprocess 11 | import re 12 | import time 13 | import os 14 | import signal 15 | 16 | #import socket 17 | #import threading 18 | import SocketServer 19 | 20 | # nc -l 5101 21 | 22 | if (len(sys.argv) < 2): 23 | path = '/var/log/cdr/' 24 | else: 25 | path = sys.argv[1].rstrip('/') + '/' 26 | 27 | class MyTCPHandler(SocketServer.StreamRequestHandler): 28 | 29 | def handle(self): 30 | # self.rfile is a file-like object created by the handler; 31 | # we can now use e.g. readline() instead of raw recv() calls 32 | self.data = self.rfile.readline() 33 | 34 | if (self.data != ''): 35 | fpath = path + self.client_address[0] + '/' 36 | if (not os.path.exists(fpath)): 37 | os.makedirs(fpath) 38 | fname = fpath + time.strftime('%Y%m%d.log', time.localtime() ) 39 | 40 | f = open(fname, 'a') 41 | f.write(self.data) 42 | f.flush() 43 | f.close() 44 | 45 | # self.wfile.write(self.data.upper()) 46 | 47 | if __name__ == "__main__": 48 | HOST, PORT = "0.0.0.0", 5101 49 | 50 | print time.strftime('%d.%m.%Y %H:%M:%S', time.localtime() ) + ': Logger started' 51 | 52 | def signal_handler(signal, frame): 53 | print time.strftime('%d.%m.%Y %H:%M:%S', time.localtime() ) + ': Logger terminated' 54 | sys.exit(0) 55 | 56 | signal.signal(signal.SIGINT, signal_handler) 57 | 58 | server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler) 59 | server.serve_forever() 60 | 61 | sys.exit() 62 | -------------------------------------------------------------------------------- /PBX/sbin/conf: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | umask 177 3 | 4 | if [ "$1" == "" ]; then 5 | echo SQL config must be specified! 6 | exit 1 7 | fi 8 | 9 | echo Writing asterisk config from "${1}" 10 | tmp=`mktemp` 11 | 12 | /usr/bin/psql -U postgres -f "${1}" pbx 1> /dev/null 2>${tmp} 13 | 14 | if [ ! -s ${tmp} ]; then 15 | echo "OK" 16 | rm -f ${tmp} 17 | exit 0 18 | else 19 | echo "ERROR processing config:" 20 | egrep -i 'ERROR|ОШИБКА' ${tmp} | head -n 3 21 | str=`egrep -i 'ERROR|ОШИБКА' ${tmp} | head -n 1 | cut -d ':' -f 3` 22 | str=$(( $str - 1 )) 23 | sed "$str,+1!d" "${1}" 24 | rm -f ${tmp} 25 | exit 1 26 | fi 27 | -------------------------------------------------------------------------------- /PBX/sbin/config: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | umask 133 3 | # Manage PBX config behind nginx 4 | 5 | if [ "$1" == "cp" ]; then 6 | f=`readlink -f "${4}" | egrep '^/pbx/conf/'` 7 | [ $? -ne 0 ] && exit 8 | 9 | mkdir "${4}" 2>/dev/null && chmod 755 "${4}" 10 | 11 | f=`readlink -f "${2}" | egrep "^${4}|^/tmp/"` 12 | [ $? -ne 0 ] && exit 13 | 14 | f=`readlink -f "${3}" | egrep "^${4}"` 15 | [ $? -ne 0 ] && exit 16 | 17 | f=`file -i "${2}" | grep ': text/plain'` 18 | if [ $? -eq 0 ]; then 19 | cp "${2}" "${3}" 20 | fi 21 | 22 | if [ $? -eq 0 ]; then 23 | chmod 644 "${3}" 24 | echo 'OK' 25 | fi 26 | fi 27 | 28 | if [ "$1" == "mv" ]; then 29 | f=`readlink -f "${4}" | egrep '^/pbx/conf(/|$)'` 30 | [ $? -ne 0 ] && exit 31 | 32 | mkdir "${4}" 2>/dev/null && chmod 755 "${4}" 33 | 34 | f=`readlink -f "${2}" | egrep "^${4}|^/tmp/"` 35 | [ $? -ne 0 ] && exit 36 | 37 | f=`readlink -f "${3}" | egrep "^${4}"` 38 | [ $? -ne 0 ] && exit 39 | 40 | mv "${2}" "${3}" 41 | if [ $? -eq 0 ]; then 42 | echo 'OK' 43 | fi 44 | fi 45 | 46 | if [ "$1" == "rm" ]; then 47 | f=`readlink -f "${3}" | egrep '^/pbx/conf/'` 48 | [ $? -ne 0 ] && exit 49 | 50 | f=`readlink -f "${2}" | egrep "^${3}"` 51 | [ $? -ne 0 ] && exit 52 | 53 | rm -f "${2}" 54 | if [ $? -eq 0 ]; then 55 | echo 'OK' 56 | fi 57 | fi 58 | 59 | if [ "$1" == "rmdir" ]; then 60 | f=`readlink -f "${3}" | egrep '^/pbx/conf/'` 61 | [ $? -ne 0 ] && exit 62 | 63 | f=`readlink -f "${2}" | egrep "^${3}"` 64 | [ $? -ne 0 ] && exit 65 | 66 | rmdir "${2}" 67 | if [ $? -eq 0 ]; then 68 | echo 'OK' 69 | fi 70 | fi 71 | 72 | if [ "$1" == "mkdir" ]; then 73 | f=`readlink -f "${3}" | egrep '^/pbx/conf/'` 74 | [ $? -ne 0 ] && exit 75 | 76 | f=`readlink -f "${2}" | egrep "^${3}"` 77 | [ $? -ne 0 ] && exit 78 | 79 | mkdir -p "${2}" 80 | if [ $? -eq 0 ]; then 81 | chmod 755 "${2}" 82 | echo 'OK' 83 | fi 84 | fi 85 | 86 | if [ "$1" == "touch" ]; then 87 | f=`readlink -f "${3}" | egrep '^/pbx/conf/'` 88 | [ $? -ne 0 ] && exit 89 | 90 | mkdir "${3}" 2>/dev/null && chmod 755 "${3}" 91 | 92 | f=`readlink -f "${2}" | egrep "^${3}"` 93 | [ $? -ne 0 ] && exit 94 | 95 | touch "${2}" 96 | if [ $? -eq 0 ]; then 97 | chmod 644 "${2}" 98 | echo 'OK' 99 | fi 100 | fi 101 | -------------------------------------------------------------------------------- /PBX/sbin/ingress: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | umask 177 3 | 4 | # ingress PHONE [up|down] 5 | 6 | EXT=$1 7 | if [ "${EXT}" == "" ]; then 8 | echo "Usage: $0 PHONE [up|down]" 9 | echo " e.g. $0 8123388838 down" 10 | exit 11 | fi 12 | 13 | if [ "$2" == "" ]; then 14 | # Check current status 15 | p=`psql -U postgres -qtc "SELECT \"Exten\",\"Description\" from \"Func\" where \"BIND\" = '_' and \"Exten\" = '$EXT';" pbx | sed -r 's/\s+/ /g'` 16 | if [ "${p}" != "" ]; then 17 | echo "INGRESS DOWN: ${p}" 18 | else 19 | p=`psql -U postgres -qtc "SELECT COALESCE(\"BIND\",'NULL')||'+'||\"Exten\"||' '||COALESCE(\"Description\",'') from \"Func\" where \"Exten\" = '$EXT';" pbx | sed -r 's/\s+/ /g'` 20 | echo "INGRESS UP: ${p}" 21 | fi 22 | 23 | if [ "${p}" == "" ]; then 24 | echo "INGRESS NOT SET: No one DID for \"${EXT}\"" 25 | fi 26 | 27 | exit 28 | fi 29 | 30 | [ "$2" == "down" ] && del='' || del='-- ' 31 | [ "${del}" == "" ] && d='DOWN' || d='UP' 32 | 33 | p=`psql -U postgres -qtc "SELECT \"Exten\" from \"Func\" where \"Exten\" = '$EXT' and NOT(\"BIND\"='_');" pbx | egrep -o '[0-9]+'` 34 | if [ $? -gt 0 ]; then 35 | echo "No DID found for \"$BIND\"" 36 | exit 1 37 | fi 38 | 39 | ( 40 | echo "${del}[Func ${p}] ; `basename $0` \"${EXT}\"" 41 | echo " Macro = Indication ; (fn,Timeout,NoTransfer,Limit)" 42 | echo " P1 = NonExistent" 43 | echo " P2 = 86400" 44 | ) | /usr/local/sbin/upsert -b '_' -IDT | psql -U postgres -q pbx 45 | 46 | echo "INGRESS ${d} for DID [$p]" 47 | -------------------------------------------------------------------------------- /PBX/sbin/macro: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ "$1" == "" ]; then 3 | egrep -n "^macro .+\(" /etc/asterisk/ael/func/*.ael | cut -d ':' -f 3 | sed 's/{//' | sort -k 2 4 | echo "" 5 | echo " Use \"$0 MacroName\" for details" 6 | exit 7 | fi 8 | 9 | 10 | m=`egrep -n "^macro $1\(" /etc/asterisk/ael/func/*.ael` 11 | file=`echo "$m" | cut -d ':' -f 1` 12 | if [ "$file" == "" ]; then 13 | echo "Macro not found" 14 | exit 15 | fi 16 | 17 | str=`echo "$m" | cut -d ':' -f 2` 18 | str=$(( $str - 2 )) 19 | lines=2 20 | if [ $str -le 0 ]; then 21 | lines=1 22 | str=1 23 | fi 24 | 25 | sed "$str,+$lines!d ; s/{//" "${file}" 26 | -------------------------------------------------------------------------------- /PBX/sbin/pbx_init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | umask 177 3 | 4 | f=`find /pbx/conf/NULL/ -mindepth 1 -maxdepth 1 -type f | head -n 1` 5 | [ "${f}" != "" ] && card "${f}" ! ! 6 | 7 | find /pbx/conf/ -mindepth 1 -maxdepth 1 -type f -exec card "{}" ! ! \; 8 | 9 | dir=(`find /pbx/conf/ -mindepth 1 -maxdepth 1 -type d -not -name 'NULL'`) 10 | 11 | for d in "${dir[@]}"; do 12 | f=`find "${d}" -mindepth 1 -maxdepth 1 -type f | head -n 1` 13 | [ "${f}" != "" ] && card "${f}" ! ! 14 | done 15 | 16 | find /pbx/conf/REDIRECT/ -mindepth 1 -maxdepth 1 -type f -exec card "{}" ! ! \; 17 | -------------------------------------------------------------------------------- /PBX/sbin/redirect: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | umask 122 3 | 4 | CONF="$1" 5 | BIND='REDIRECT' 6 | LIMIT=3 7 | 8 | count=0 9 | CFG='' 10 | 11 | if (echo "${CONF}" | egrep -q '^[0-9]{7}([0-9]{3})?(\.cfg)?$'); then 12 | CFG="${CONF}" 13 | echo "${CFG}" | egrep -q '^[0-9]{7}([^0-9]|$)' && CFG="812${CFG}" 14 | echo "${CFG}" | egrep -q '[0-9]$' && CFG="${CFG}.cfg" 15 | CFG="/pbx/auto/${BIND}/${CFG}" 16 | count=`ls -1 "${CFG}" 2>/dev/null | wc -l` 17 | fi 18 | 19 | if [ "$CFG" == "" ] || [ $count -eq 0 ]; then 20 | CFG1=`find /pbx/auto/${BIND}/ -type f -name "*${CONF}*" | egrep -v '^$'` 21 | count=`echo -n ${CFG} | wc -l` 22 | if [ ${count} -gt 1 ]; then 23 | echo " Ambiguous \"$1\":" 24 | echo "${CFG1}" 25 | exit 0 26 | fi 27 | [ ${count} -eq 1 ] && CFG="${CFG1}" 28 | 29 | if [ ${count} -eq 0 ]; then 30 | count=`grep -Rli "${CONF}" /pbx/auto/${BIND}/ | wc -l` 31 | # [ ${count} -eq 0 ] && echo "Not found \"${CONF}\" inside configurations" && exit 0 32 | if [ ${count} -gt 1 ]; then 33 | echo " Multiple \"${CONF}\" inside configurations:" 34 | grep -Rli "${CONF}"/pbx/auto/${BIND}/ 35 | exit 0 36 | fi 37 | [ ${count} -eq 1 ] && CFG=`grep -Rli "${CONF}" /pbx/auto/${BIND}/` 38 | fi 39 | fi 40 | 41 | CONF="${CFG}" 42 | #echo ${CONF} 43 | 44 | mt=`stat -c '%Y' "${CONF}" 2>/dev/null || echo 0` 45 | f=`readlink -f "${CONF}" | egrep "^/pbx/auto/${BIND}/"` 46 | [ $? -ne 0 ] && exit 47 | 48 | echo `date "+%Y.%m.%d %H:%M"`"${SUDO_UID} ${SUDO_USER}: OPEN ${f}" >> /var/log/redirect.log 49 | 50 | if [ "$2" != "!" ] && [ "$2" != "!!" ]; then 51 | nano -kiw -Y "/usr/share/nano/conf.nanorc" -o `dirname ${f}` "${CONF}" 52 | mt1=`stat -c '%Y' "${CONF}" 2>/dev/null || echo 0` 53 | 54 | if [ "$mt1" -eq "$mt" ]; then 55 | echo 'Config unchanged' 56 | exit 0 57 | fi 58 | fi 59 | 60 | 61 | f=`readlink -f "${CONF}"` 62 | conf=/pbx/conf/${BIND}/`basename ${f}` 63 | did=`basename ${f} .cfg` 64 | 65 | if echo "${did}" | egrep -qv '[0-9]'; then 66 | echo "ERROR: non-numeric DID \"${did}\"" 67 | exit 68 | fi 69 | 70 | echo "[* ${did} ${BIND}] ; Autogenerated from ${f}" 71 | ( 72 | echo "[* ${did} ${BIND}] ; Autogenerated from ${f}" 73 | echo "CallLimit = 3" 74 | egrep -vi "^(\s|\t)*(CallLimit|CallLevel)(\s|\t)*=" "${f}" 75 | ) | /usr/local/sbin/schema > "${conf}" 2>"${conf}.err" 76 | res=$? 77 | 78 | if [ ${res} -eq 0 ]; then 79 | echo "${conf} OK" 80 | rm -f "${conf}.err" 81 | echo `date "+%Y.%m.%d %H:%M"`"${SUDO_UID} ${SUDO_USER}: OK ${f}" >> /var/log/redirect.log 82 | else 83 | cat "${conf}.err" 84 | echo `date "+%Y.%m.%d %H:%M"`"${SUDO_UID} ${SUDO_USER}: ERROR [${res}] in config ${f}" >> /var/log/redirect.log 85 | exit 86 | fi 87 | -------------------------------------------------------------------------------- /PBX/sbin/route: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | umask 077 3 | 4 | dst="$1" 5 | src="$2" 6 | level="$3" 7 | tag="$4" 8 | bind="$5" 9 | 10 | if [ -z "$dst" ]; then 11 | echo "Usage: $0 [src=3225247] [level=0] [tag=\$TAG|'A4'] [BIND='\$BIND'|'']" 12 | exit 1 13 | fi 14 | 15 | if [ -z "$src" ]; then 16 | src='3225247' 17 | echo "* SRC=${src}" >&2 18 | fi 19 | 20 | if [ -z "$level" ]; then 21 | level='0' 22 | fi 23 | 24 | if [ -z "$tag" ]; then 25 | tag="${TAG:-A4}" 26 | echo "* TAG=${tag}" >&2 27 | fi 28 | 29 | if [ -z "$bind" ]; then 30 | bind="${BIND}" 31 | echo "* BIND=${bind}" >&2 32 | fi 33 | 34 | sql=`cat < /dev/null"}' | tr '~' '"'` 3 | 4 | asterisk -rx 'sip reload' 5 | echo "${rt}" | sh 6 | -------------------------------------------------------------------------------- /PBX/sbin/sip-trace: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | umask 177 3 | 4 | if [ "$1" == "" ]; then 5 | echo " Usage: $0 [port] [timeout]" 6 | exit 7 | fi 8 | 9 | host=`echo $1 | egrep -o '([0-9]{1,3}\.){3}[0-9]{1,3}'` 10 | if [ $? -ne 0 ]; then 11 | echo "\"$1\" in not a valid IPv4" 12 | exit 1 13 | fi 14 | 15 | port=`echo $2 | egrep -o '[0-9]{2,5}' || echo 5060` 16 | timeout=`echo $3 | egrep -o '[0-9]{1,5}' || echo 300` 17 | 18 | (sleep ${timeout}s && pkill -P $$) & 19 | echo "*** host $host and proto udp and port $port ***" 20 | stdbuf -oL tcpdump -i any -s 65535 -w - host $host and proto \\udp and port $port | stdbuf -oL strings |\ 21 | awk --re-interval '/^.?([[:upper:]]{3,})/{gsub(/[[:upper:]]{3,}/,"\033[1;31m&\033[1;000m")};/^CSeq:/{gsub(/^CSeq:/,"\033[1;36m&\033[1;000m")}{print $0}' 22 | -------------------------------------------------------------------------------- /PBX/sbin/sounds: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | umask 133 3 | # Manage sounds behind nginx 4 | 5 | function moh() { 6 | echo 1 7 | MOH=`echo "${1}" | sed -r 's|^/var/lib/asterisk/MOH||' | cut -d '/' -f 2` 8 | [ "${MOH}" == "" ] && return 9 | [ -f "/etc/asterisk/moh/${MOH}.conf" ] && return 10 | 11 | cat < /etc/asterisk/moh/${MOH}.conf 12 | [${MOH}] 13 | mode=files 14 | directory=/var/lib/asterisk/MOH/${MOH} 15 | sort=alpha 16 | EOF 17 | asterisk -rx 'moh reload' > /dev/null 2>&1 18 | } 19 | 20 | if [ "$1" == "cp" ]; then 21 | f=`readlink -f "${4}" | egrep -q '^/var/lib/asterisk/(sounds|MOH)'` 22 | [ $? -ne 0 ] && exit 23 | 24 | mkdir "${4}" 2>/dev/null && chmod 755 "${4}" 25 | 26 | f=`readlink -f "${2}" | egrep "^${4}|^/tmp/"` 27 | [ $? -ne 0 ] && exit 28 | 29 | f=`readlink -f "${3}" | egrep "^${4}"` 30 | [ $? -ne 0 ] && exit 31 | 32 | f=`echo "${3}" | egrep '\.(alaw|raw)$'` 33 | if [ $? -eq 0 ]; then 34 | out=`echo "${3}" | sed -r 's/\.[^.]+$/\.alaw/'` 35 | cp "${2}" "${out}" 36 | else 37 | out=`echo "${3}" | sed -r 's/\.[^.]+$/\.wav/'` 38 | f=`file "${2}" | grep 'RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 8000 Hz'` 39 | if [ $? -eq 0 ]; then 40 | cp "${2}" "${out}" 41 | else 42 | ffmpeg -i "${2}" -ac 1 -ar 8000 -ab 128k -b 16 -y "${out}" > /dev/null 2>&1 43 | fi 44 | fi 45 | 46 | if [ $? -eq 0 ]; then 47 | chmod 644 "${out}" 48 | echo 'OK' 49 | readlink -f "${3}" | egrep -q '^/var/lib/asterisk/MOH/.+' && moh "${3}" 50 | fi 51 | fi 52 | 53 | if [ "$1" == "mv" ]; then 54 | f=`readlink -f "${4}" | egrep '^/var/lib/asterisk/(sounds|MOH)'` 55 | [ $? -ne 0 ] && exit 56 | 57 | mkdir "${4}" 2>/dev/null && chmod 755 "${4}" 58 | 59 | f=`readlink -f "${2}" | egrep "^${4}|^/tmp/"` 60 | [ $? -ne 0 ] && exit 61 | 62 | f=`readlink -f "${3}" | egrep "^${4}"` 63 | [ $? -ne 0 ] && exit 64 | 65 | mv "${2}" "${3}" 66 | if [ $? -eq 0 ]; then 67 | echo 'OK' 68 | readlink -f "${3}" | egrep -q '^/var/lib/asterisk/MOH/.+' && moh "${3}" 69 | fi 70 | fi 71 | 72 | if [ "$1" == "rm" ]; then 73 | f=`readlink -f "${3}" | egrep '^/var/lib/asterisk/(sounds|MOH)'` 74 | [ $? -ne 0 ] && exit 75 | 76 | f=`readlink -f "${2}" | egrep "^${3}"` 77 | [ $? -ne 0 ] && exit 78 | 79 | rm -f "${2}" 80 | if [ $? -eq 0 ]; then 81 | echo 'OK' 82 | fi 83 | fi 84 | 85 | if [ "$1" == "rmdir" ]; then 86 | f=`readlink -f "${3}" | egrep '^/var/lib/asterisk/(sounds|MOH)'` 87 | [ $? -ne 0 ] && exit 88 | 89 | f=`readlink -f "${2}" | egrep "^${3}"` 90 | [ $? -ne 0 ] && exit 91 | 92 | rmdir "${2}" 93 | if [ $? -eq 0 ]; then 94 | echo 'OK' 95 | fi 96 | fi 97 | 98 | if [ "$1" == "mkdir" ]; then 99 | f=`readlink -f "${3}" | egrep '^/var/lib/asterisk/(sounds|MOH)'` 100 | [ $? -ne 0 ] && exit 101 | 102 | f=`readlink -f "${2}" | egrep "^${3}"` 103 | [ $? -ne 0 ] && exit 104 | 105 | mkdir -p "${2}" 106 | if [ $? -eq 0 ]; then 107 | chmod 755 "${2}" 108 | echo 'OK' 109 | fi 110 | fi 111 | 112 | if [ "$1" == "touch" ]; then 113 | f=`readlink -f "${3}" | egrep '^/var/lib/asterisk/(sounds|MOH)'` 114 | [ $? -ne 0 ] && exit 115 | 116 | mkdir "${3}" 2>/dev/null && chmod 755 "${3}" 117 | 118 | f=`readlink -f "${2}" | egrep "^${3}"` 119 | [ $? -ne 0 ] && exit 120 | 121 | touch "${2}" 122 | if [ $? -eq 0 ]; then 123 | chmod 644 "${2}" 124 | echo 'OK' 125 | readlink -f "${2}" | egrep -q '^/var/lib/asterisk/MOH/.+' && moh "${2}" 126 | fi 127 | fi 128 | -------------------------------------------------------------------------------- /PBX/sbin/trace: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | umask 177 3 | 4 | /usr/sbin/asterisk -rvvv 2>&1 | unbuffer -p grep FUNCTION | unbuffer -p egrep -o '"[^"]+"\)'| unbuffer -p sed 's/^"// ; s/")$//' | unbuffer -p egrep -v '^$' | unbuffer -p egrep --color=auto "$1" | sed 's/ FUNCTION / -> /' 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VX-PBX 2 | Very eXtendable PBX sources by VOIX, Ltd. @ vo-ix.ru. 3 | -------------------------------------------------------------------------------- /RUDDER/README.md: -------------------------------------------------------------------------------- 1 | This is not a part of PBX. Just my lazy to create new git. 2 | There will be placed some parts to make Rudder http://www.rudder-project.org/ implementation more convenient. 3 | 4 | "api.py" is prototype to call rudder's API 5 | "rudder-replica.py" is generally working replication solution, implementing network model similar to OOP. 6 | 7 | *This toolset is too sophisticated, and so it is deprecated. 8 | Look under "replicator/" for actual 3-component view.* 9 | -------------------------------------------------------------------------------- /RUDDER/replicator/README.md: -------------------------------------------------------------------------------- 1 | *3-component replication toolset* 2 | "replica" shell >> "rule-replace" interface >> "rule-replica" tool 3 | 4 | *TL.DR* 5 | # replica deb 6 | # INIT=rudder.xxx.local RULE="NET/ldap: SLAPD slave" replica ldap 7 | # HOST=rudder-deb.xxx.local replica deb 8 | -------------------------------------------------------------------------------- /RUDDER/replicator/etc/apache2/rudder_accept.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Require ip 127.0.0.1 5 | Require ip ::1 6 | Require host rudder-root.xxx.local 7 | Require host rudder-deb.xxx.local 8 | Require expr %{REQUEST_URI} =~ m#/nodes/pending$# 9 | Require expr %{REQUEST_URI} =~ m#/nodes/pending/# 10 | 11 | Include X-API-Token.conf 12 | 13 | 14 | 15 | Require ip 127.0.0.1 16 | Require ip ::1 17 | Require host controller1.xxx.local 18 | Require host controller2.xxx.local 19 | Require expr %{REQUEST_URI} =~ m#/nodes/pending$# 20 | Require expr %{REQUEST_URI} =~ m#/nodes/pending/# 21 | 22 | Include X-API-Token.conf 23 | 24 | 25 | 26 | Order deny,allow 27 | Allow from all 28 | 29 | 30 | SSLEngine on 31 | SSLProxyEngine On 32 | SSLCertificateFile /opt/rudder/etc/ssl/rudder.crt 33 | SSLCertificateKeyFile /opt/rudder/etc/ssl/rudder.key 34 | 35 | # http://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslproxycheckpeercn 36 | ProxyRequests Off 37 | ProxyPreserveHost On 38 | ProxyPass / https://localhost:443/ 39 | ProxyPassReverse / https://localhost:443/ 40 | 41 | CacheDisable * 42 | 43 | SSLProxyVerify none 44 | # https://serverfault.com/questions/538086/proxyerror-ah00898-error-during-ssl-handshake-with-remote-server 45 | SSLProxyCheckPeerCN off 46 | SSLProxyCheckPeerName off 47 | 48 | -------------------------------------------------------------------------------- /RUDDER/replicator/etc/replica/host/all.conf: -------------------------------------------------------------------------------- 1 | # All hosts 2 | 3 | # main locations "xxx.local" 4 | dst[0]="rudder-deb.xxx.local" 5 | dst[1]="rudder-deb.yyy.local" 6 | -------------------------------------------------------------------------------- /RUDDER/replicator/etc/replica/rule/deb.conf: -------------------------------------------------------------------------------- 1 | path[0]="Debian/deb: dnsmasq" 2 | path[1]="Debian/deb: SSSD" 3 | path[2]="Debian/deb: Debian common" 4 | 5 | if [ -z "${INIT}" ]; then 6 | path[99]="VERSION" 7 | fi 8 | 9 | if [ ${#dst[@]} -eq 0 ]; then 10 | [ -f "/etc/replica/host/all.conf" ] && . /etc/replica/host/all.conf 11 | fi 12 | -------------------------------------------------------------------------------- /RUDDER/replicator/etc/replica/rule/ldap.conf: -------------------------------------------------------------------------------- 1 | path[0]="NET/ldap: SLAPD slave" 2 | 3 | #if [ -z "${INIT}" ]; then 4 | # path[99]="VERSION" 5 | #fi 6 | 7 | if [ ${#dst[@]} -eq 0 ]; then 8 | [ -f "/etc/replica/host/main.conf" ] && . /etc/replica/host/main.conf 9 | fi 10 | -------------------------------------------------------------------------------- /RUDDER/replicator/etc/replica/rule/unb.conf: -------------------------------------------------------------------------------- 1 | path[0]="NET/unb: Unbound 1.6" 2 | 3 | #if [ -z "${INIT}" ]; then 4 | # path[99]="VERSION" 5 | #fi 6 | 7 | if [ ${#dst[@]} -eq 0 ]; then 8 | [ -f "/etc/replica/host/main.conf" ] && . /etc/replica/host/main.conf 9 | fi 10 | -------------------------------------------------------------------------------- /RUDDER/replicator/inotify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os, datetime, subprocess, re, thread 3 | import pyinotify 4 | 5 | # https://www.mauras.ch/speed-up-rudder-new-nodes-detection.html 6 | # apt-get install python-pyinotify 7 | 8 | command = "/opt/rudder/bin/cf-agent -K -b sendInventoryToCmdb -f /var/rudder/cfengine-community/inputs/promises.cf" 9 | report_re = re.compile('R:.*') 10 | 11 | wm = pyinotify.WatchManager() # Watch Manager 12 | mask = pyinotify.IN_DELETE | pyinotify.IN_CREATE # watched events 13 | 14 | def exec_proc(file, dummy1): 15 | process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=None, shell=True) 16 | output = process.communicate() 17 | date = datetime.datetime.now() 18 | # report_line = report_re.search(output[0]) 19 | # print "%s: %s - %s" % (date, file, report_line.group()) 20 | 21 | class EventHandler(pyinotify.ProcessEvent): 22 | def process_IN_CREATE(self, event): 23 | date = datetime.datetime.now() 24 | print "%s: %s - %s created" % (date, event.path, event.name) 25 | 26 | if "incoming" in event.path: 27 | dummy_tup = event.name, 'null' 28 | thread.start_new_thread(exec_proc, dummy_tup) 29 | 30 | def process_IN_DELETE(self, event): 31 | date = datetime.datetime.now() 32 | print "%s: %s - %s deleted" % (date, event.path, event.name) 33 | 34 | 35 | handler = EventHandler() 36 | notifier = pyinotify.Notifier(wm, handler) 37 | wdd = wm.add_watch('/var/rudder/inventories/', mask, rec=True) 38 | 39 | notifier.loop() 40 | -------------------------------------------------------------------------------- /RUDDER/replicator/replica: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Run LOCAL "rule-replace" for hosts in dst[] array, rules in path[] array. 3 | # * Calling replicas from local to remote host takes a long time at long distances! 4 | 5 | if [ -z "$1" ]; then 6 | if [ -z "$RULE" ]; then 7 | echo -e "\e[35m Rule list must be set.\e[00m" 8 | echo "Usage: [INIT|HOST=host.name.url] [RULE=\"/path/to/rule name\"] $0 [host_config]" 9 | echo "Examlpes: 10 | $0 deb # use /etc/replica/rule/deb.conf with default hosts 11 | $0 unb main # use /etc/replica/rule/unb.conf and /etc/replica/host/main.conf 12 | INIT=rudder.xxx.local RULE=\"NET/unb: Unbound 1.6\" $0 # Initial replica of new rule 13 | HOST=rudder.xxx.local RULE=\"NET/unb: Unbound 1.6\" $0 # Exact this one rule replica to the exact host" 14 | exit 15 | fi 16 | 17 | else 18 | # Set dst[*] 19 | if [ -n "$2" ]; then 20 | host="/etc/replica/host/$2.conf" 21 | if [ -f "${host}" ]; then 22 | . "${host}" 23 | else 24 | echo "Host config not found: \"${host}\"" 25 | exit 1 26 | fi 27 | fi 28 | 29 | # Set path[*] and m.b. default dst[*], if second argument is empty 30 | rule="/etc/replica/rule/$1.conf" 31 | if [ -f "${rule}" ]; then 32 | . "${rule}" 33 | else 34 | echo "Rule config not found: \"${rule}\"" 35 | exit 1 36 | fi 37 | fi 38 | 39 | # Rewrite dst[*] and path[*] if variables are set 40 | if [ -n "${INIT}" ]; then 41 | dst=("${INIT}") 42 | fi 43 | 44 | if [ -n "${HOST}" ]; then 45 | dst=("${HOST}") 46 | fi 47 | 48 | if [ -n "${RULE}" ]; then 49 | path=("${RULE}") 50 | fi 51 | 52 | 53 | if [ ${#dst[@]} -eq 0 ]; then 54 | echo "Host list is empty. Nothing to do." 55 | exit 1 56 | fi 57 | 58 | if [ ${#path[@]} -eq 0 ]; then 59 | echo "Rule list is empty. Nothing to do." 60 | exit 1 61 | fi 62 | 63 | ############ REPLICA ############ 64 | for rule in "${path[@]}"; do 65 | echo -e "\e[35m 66 | >>> Rule = \"${rule}\" <<<\e[00m" 67 | 68 | i=0 69 | dir=`mktemp -d --tmpdir=/tmp/ rule-replace.XXXXXXXXXX` 70 | for host in "${dst[@]}"; do 71 | if [ $i -gt 0 ]; then # Log to file 72 | echo -e "\e[36m 73 | >>> Host = \"${host}\" <<<\e[00m" >> "/${dir}/${host}.log" 74 | if [ -n "${INIT}" ]; then 75 | echo "*** Initial replica for \"${host}\"" 76 | INIT=1 SRC=localhost DST="${host}" rule-replace "${rule}" >> "/${dir}/${host}.log" 2>&1 & 77 | else 78 | # DELETE=1 # No, may lead to deletion of uncleared rule|directive in case of interruption! 79 | CLEAN=1 SRC=localhost DST="${host}" rule-replace "${rule}" >> "/${dir}/${host}.log" 2>&1 & 80 | fi 81 | else # Log directly to console 82 | echo -e "\e[36m 83 | >>> Host = \"${host}\" <<<\e[00m" 84 | ( 85 | if [ -n "${INIT}" ]; then 86 | echo "*** Initial replica for \"${host}\"" 87 | INIT=1 SRC=localhost DST="${host}" rule-replace "${rule}" 88 | else 89 | # DELETE=1 # No, may lead to deletion of uncleared rule|directive in case of interruption! 90 | CLEAN=1 SRC=localhost DST="${host}" rule-replace "${rule}" 91 | fi 92 | 93 | echo -e "\e[34m 94 | ...Waiting for all host tasks to be completed...\e[00m" 95 | ) 2>&1 & 96 | fi 97 | if [ $? -ne 0 ]; then 98 | echo "!!! Error occured. Aborting!" 99 | exit 255 100 | fi 101 | pids[$((i++))]=$! 102 | done 103 | 104 | for pid in ${pids[*]}; do 105 | wait $pid 106 | done 107 | [ `ls -1 "${dir}" | wc -l` -gt 0 ] && cat "${dir}"/* 108 | 109 | # Don't trap this on exit, as it can be used to debug faults 110 | rm -Rf "${dir}" 111 | done 112 | -------------------------------------------------------------------------------- /RUDDER/replicator/rule-replace: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # INIT=1 SRC="rudder-deb.xxx.local" DST=localhost NORENAME=1 NOCOPY=1 DELETE=1 CLEAN=1 NEW=8f9391be-d2a1-415b-968f-7346f37de93c OLD=2cf71bd0-2eac-41dc-8c97-6422dcfbf1ee rule-replace 'Debian/deb: dnsmasq' 3 | path="$1" 4 | 5 | if [ -z "${path}" ]; then # Print hint and exit 6 | echo " Usage: [variable1=x variable2=y ...] $0 \"Path/to/directive name\" 7 | The following variables can be set: 8 | INIT=1 - Make first replica (w/o any groups and so on) 9 | SRC=src.host.fqdn - Set source host, instead of \"rudder-root.xxx.local\" 10 | DST=dst.host.fqdn - Set destination host, instead of localhost 11 | NORENAME=1 - Skip old rule/it's directives renaming with prefix \"#old \" 12 | NOCOPY=1 - Skip replication (e.g. we need to transfer groups between rules, and no more) 13 | DELETE=1 - Delete deprecated rule/it's directives (with prefix \"#old \") before further processing 14 | CLEAN=1 - The same as DELETE, but AFTER all other operations 15 | ORPHAN=1 - Remove orphan directives (those with tag \"actual\"='+') 16 | NEW=UUID - Use this UUID on SRC host (skip searching rule by name) 17 | OLD=UUID - Use this UUID on DST host (skip searching rule by name) 18 | " 19 | exit 20 | fi 21 | 22 | echo "* Replacing rule \"$path\" and all of it's directives" 23 | 24 | src="${SRC}" 25 | if [ -z "${src}" ]; then 26 | src="rudder-root.xxx.local" 27 | fi 28 | s_ip="`host ${src}`" 29 | if [ $? -ne 0 -o -z "${s_ip}" ]; then 30 | echo "*** Error: unknown host name \"${src}\"" 31 | exit 255 32 | fi 33 | 34 | dst="${DST}" 35 | if [ -z "${dst}" ]; then 36 | dst="localhost" 37 | fi 38 | d_ip="`host ${dst}`" 39 | if [ $? -ne 0 -o -z "${d_ip}" ]; then 40 | echo "*** Error: unknown host name \"${dst}\"" 41 | exit 255 42 | fi 43 | 44 | if [ "${src}" = "${dst}" ]; then 45 | echo "*** SRC host and DST host MUST NOT BE THE SAME!" 46 | echo "SRC: 47 | ${s_ip}" 48 | echo "DST: 49 | ${d_ip}" 50 | exit 255 51 | fi 52 | 53 | 54 | new="${NEW}" 55 | if [ -z "${new}" ]; then 56 | new=`rule-replica -h ${src}:8443 -k = -H ${dst}:8443 -K = -i "${path}" | awk '{if ($1 ~ "UUID:") print $2}'` 57 | fi 58 | 59 | old="${OLD}" 60 | if [ -z "${old}" ]; then 61 | old=`rule-replica -h ${src}:8443 -k = -H ${dst}:8443 -K = -I "${path}" | awk '{if ($1 ~ "UUID:") print $2}'` 62 | fi 63 | 64 | if [ -n "${ORPHAN}" ]; then 65 | rule-replica -h ${src}:8443 -k = -H ${dst}:8443 -K = -o --delete orphan 66 | fi 67 | 68 | if [ -n "${DELETE}" -a -z "${INIT}" ]; then 69 | del=`rule-replica -h ${src}:8443 -k = -H ${dst}:8443 -K = -I "${path}" | awk '{if ($1 ~ "UUID_old:") print $2}'` 70 | if [ -n "${del}" ]; then 71 | echo "* Deleting deprecated \"#old\"-prefixed rule UUID=${del}, together with it's directives..." 72 | rule-replica -h ${src}:8443 -k = -H ${dst}:8443 -K = -d "${del}" 73 | fi 74 | fi 75 | 76 | if [ -z "${old}" ]; then 77 | if [ -z "${INIT}" ]; then 78 | echo "*** Old rule not found." 79 | echo "* If it was already renamed, you can specify it's UUID as the third argument." 80 | echo " Existing target rules:" 81 | rule-replica -h ${src}:8443 -k = -H ${dst}:8443 -K = -I "${path}" | sort 82 | exit 1 83 | fi 84 | else 85 | if [ -z "${NORENAME}" -a -z "${INIT}" ]; then 86 | echo "* Old rule uuid=\"${old}\". Renaming it together with directives. Add prefix \"#old\"" 87 | rule-replica -h ${src}:8443 -k = -H ${dst}:8443 -K = -r "${old}" 88 | if [ $? -ne 0 ]; then 89 | echo "*** Error while renaming old rule! Aborting!" 90 | echo " Existing target rules:" 91 | rule-replica -h ${src}:8443 -k = -H ${dst}:8443 -K = -I "${path}" | sort 92 | exit 2 93 | fi 94 | fi 95 | fi 96 | 97 | if [ -z "${NOCOPY}" ]; then 98 | echo "* New rule uuid=\"${new}\". Copying it together with all of it's directives (with empty groups)" 99 | rule-replica -h ${src}:8443 -k = -H ${dst}:8443 -K = -s "${new}" -t "${path}" 100 | if [ $? -ne 0 ]; then 101 | echo "Error while copying new rule! Aborting!" 102 | exit 3 103 | fi 104 | fi 105 | 106 | # Try to find new rule on destination host 107 | new=`rule-replica -h ${src}:8443 -k = -H ${dst}:8443 -K = -I "${path}" | awk '{if ($1 ~ "UUID:") print $2}'` 108 | if [ -z "${new}" ]; then 109 | echo "*** New rule not found on ${dst}! Aborting!" 110 | rule-replica -h ${src}:8443 -k = -H ${dst}:8443 -K = -I "${path}" 111 | exit 4 112 | fi 113 | 114 | if [ -n "${INIT}" ]; then 115 | echo "*** Initial clone is done." 116 | exit 117 | fi 118 | 119 | echo "* New rule uuid=\"${new}\". Now, migrating all groups" 120 | rule-replica -h ${src}:8443 -k = -H ${dst}:8443 -K = -G All -g All -m "${old}" -M "${new}" 121 | 122 | if [ -n "${CLEAN}" ]; then 123 | del=`rule-replica -h ${src}:8443 -k = -H ${dst}:8443 -K = -I "${path}" | awk '{if ($1 ~ "UUID_old:") print $2}'` 124 | if [ -n "${del}" ]; then 125 | echo "* Deleting deprecated \"#old\"-prefixed rule UUID=${del}, together with it's directives..." 126 | rule-replica -h ${src}:8443 -k = -H ${dst}:8443 -K = -d "${del}" 127 | fi 128 | fi 129 | 130 | echo "*** All done." 131 | -------------------------------------------------------------------------------- /RUDDER/techniques/fileDistribution/copyHttpFile/1.0/changelog: -------------------------------------------------------------------------------- 1 | -- Dmitry Svyatogorov Fri, 30 Nov 2018 21:00:00 +0300 2 | * Version 1.0 3 | ** Basic implementation 4 | -------------------------------------------------------------------------------- /SQL/acl.sql: -------------------------------------------------------------------------------- 1 | /* 2 | VX-PBX routing ACL @ postgresql 3 | Copyright (C) 2012-2015 Dmitry Svyatogorov ds@vo-ix.ru 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as 7 | published by the Free Software Foundation, either version 3 of the 8 | License, or (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | 15 | You should have received a copy of the GNU Affero General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | -- Mangle rules 20 | CREATE TABLE "Mangle" ( 21 | "NRec" bigserial PRIMARY KEY, -- Binding key 22 | "BIND" text NULL, 23 | "SrcCutPref" smallint DEFAULT 0 NOT NULL, -- Strip first|last chars 24 | "DstCutPref" smallint DEFAULT 0 NOT NULL, 25 | "SrcCutSuff" smallint DEFAULT 0 NOT NULL, 26 | "DstCutSuff" smallint DEFAULT 0 NOT NULL, 27 | "SrcAddPref" text DEFAULT '' NOT NULL, -- Add prefix|suffix 28 | "DstAddPref" text DEFAULT '' NOT NULL, 29 | "SrcAddSuff" text DEFAULT '' NOT NULL, 30 | "DstAddSuff" text DEFAULT '' NOT NULL, 31 | -- http://www.postgresql.org/docs/8.4/static/functions-matching.html#POSIX-EMBEDDED-OPTIONS-TABLE 32 | -- regexp_replace(source, pattern, replacement [, flags ]) 33 | -- SELECT regexp_replace('+a0b1cc098*', '[^0-9]', '' , 'g'); >> '01098' 34 | -- SELECT regexp_replace('88129583253', '(^.*)?([0-9]{7}$)', '7812\\2' , ''); >> '78129583253' 35 | "SrcRE" text DEFAULT '' NOT NULL, -- Mangle src, according to this regular expression 36 | "DstRE" text DEFAULT '' NOT NULL, -- Mangle dst, according to this regular expression 37 | 38 | "Description" text NULL -- Optional comments 39 | ); 40 | 41 | -- ACL implementation 42 | CREATE TABLE "ACL" ( 43 | "NRec" bigserial PRIMARY KEY, 44 | "BIND" text NULL, -- Binding key 45 | "Default" boolean NOT NULL DEFAULT False, -- Default policy. False to deny by default. 46 | "Description" text NULL -- Optional comments 47 | ); 48 | 49 | -- ACL Lines 50 | CREATE TABLE "ACLines" ( 51 | "NRec" bigserial PRIMARY KEY, -- Binding key 52 | "ACL" bigint NOT NULL REFERENCES "ACL" ON UPDATE CASCADE ON DELETE CASCADE, 53 | "Order" smallint DEFAULT 0 NOT NULL, -- Processing order 54 | "Allow" boolean NOT NULL DEFAULT True, -- Allow|Deny 55 | "FromExten" bigint DEFAULT 0 NOT NULL, -- Corresponding numeric interval from..to 56 | "ToExten" bigint DEFAULT 9223372036854775807 NOT NULL, 57 | "Description" text NULL -- Optional comments 58 | ); 59 | 60 | -- ACL Lines 61 | CREATE TABLE "ACRegex" ( 62 | "NRec" bigserial PRIMARY KEY, -- Binding key 63 | "ACL" bigint NOT NULL REFERENCES "ACL" ON UPDATE CASCADE ON DELETE CASCADE, 64 | "Order" smallint DEFAULT 0 NOT NULL, -- Processing order 65 | "Allow" boolean NOT NULL DEFAULT True, -- Allow|Deny 66 | "NOT" boolean NOT NULL DEFAULT False, -- Invert regexp 67 | "RE" text NULL DEFAULT '.*', -- Regular expression to match 68 | "Description" text NULL -- Optional comments 69 | ); 70 | -------------------------------------------------------------------------------- /SQL/cdr.sql: -------------------------------------------------------------------------------- 1 | -- ASTERISK CDR -- 2 | CREATE TABLE cdr ( 3 | NRec bigserial NOT NULL primary key, 4 | calldate timestamp with time zone DEFAULT now() NOT NULL, 5 | clid text DEFAULT '' NOT NULL, 6 | src text DEFAULT '' NOT NULL, 7 | dst text DEFAULT '' NOT NULL, 8 | dcontext text DEFAULT '' NOT NULL, 9 | channel text DEFAULT '' NOT NULL, 10 | dstchannel text DEFAULT '' NOT NULL, 11 | lastapp text DEFAULT '' NOT NULL, 12 | lastdata text DEFAULT '' NOT NULL, 13 | duration bigint DEFAULT 0::bigint NOT NULL, 14 | billsec bigint DEFAULT 0::bigint NOT NULL, 15 | disposition text DEFAULT '' NOT NULL, 16 | amaflags bigint DEFAULT 0::bigint NOT NULL, 17 | accountcode text DEFAULT '' NOT NULL, 18 | uniqueid text DEFAULT '' NOT NULL, 19 | linkedid text DEFAULT '' NOT NULL, 20 | userfield text DEFAULT '' NOT NULL, 21 | -- Additional fields 22 | "x-tag" text NULL, -- Device tag, if one 23 | "x-cid" text NULL, -- Dialed cgpn 24 | "x-did" text NULL, -- Initial cdpn 25 | "x-dialed" text NULL, -- Really dialed 26 | "x-spec" text NULL, -- Dialed channel specification 27 | "x-insecure" boolean NULL, -- Bypass "CID" lookup 28 | "x-result" text NULL, -- Error code 29 | "x-record" text NULL, -- File containing call record, if one 30 | "x-domain" text NULL, -- BIND 31 | "x-data" text ARRAY NULL -- Call trace 32 | ); 33 | 34 | -- app "Queue" log format 35 | CREATE TABLE "queue_log" ( 36 | "NRec" bigserial PRIMARY KEY, 37 | "DT" timestamp DEFAULT now() NOT NULL, 38 | "time" varchar(32) DEFAULT '' NOT NULL, 39 | "callid" varchar(32) DEFAULT '' NOT NULL, 40 | "queuename" varchar(128) DEFAULT '' NOT NULL, 41 | "agent" varchar(64) DEFAULT '' NOT NULL, 42 | "event" varchar(64) DEFAULT '' NOT NULL, 43 | -- "data" varchar(128) DEFAULT '' NOT NULL 44 | "data1" varchar(128) DEFAULT '' NOT NULL, 45 | "data2" varchar(128) DEFAULT '' NOT NULL, 46 | "data3" varchar(128) DEFAULT '' NOT NULL, 47 | "data4" varchar(128) DEFAULT '' NOT NULL, 48 | "data5" varchar(128) DEFAULT '' NOT NULL 49 | ); 50 | -------------------------------------------------------------------------------- /VX/schema.d/.modulebuildrc: -------------------------------------------------------------------------------- 1 | install --install_base /root/conf.d/schema.actions 2 | -------------------------------------------------------------------------------- /VX/schema.d/action.multipath/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Pluggable modules for VX block-schema language translator 2 | Copyright (C) 2014-2018 Dmitry Svyatogorov ds@vo-ix.ru 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU Affero General Public License as 6 | published by the Free Software Foundation, either version 3 of the 7 | License, or (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | 14 | You should have received a copy of the GNU Affero General Public License 15 | along with this program. If not, see . 16 | -------------------------------------------------------------------------------- /VX/schema.d/action.multipath/hooks.pm: -------------------------------------------------------------------------------- 1 | package action::hooks; 2 | 3 | use strict; 4 | use warnings; 5 | use Exporter; 6 | 7 | our @ISA= qw( Exporter ); 8 | 9 | # these CAN be exported. 10 | our @EXPORT_OK = qw( HOOKS ); 11 | 12 | # these are exported by default. 13 | our @EXPORT = qw( HOOKS ); 14 | 15 | sub HOOKS { 16 | $_ = shift; # Params array reference 17 | my $descr = shift // ''; 18 | 19 | my @p = @{$_}; 20 | 21 | $descr =~ s/^[\s]*;[\s]*//; 22 | my ($key, $val); 23 | my $hooks = "DISK=(\n"; 24 | 25 | for (sort keys %::Fields) { 26 | if (/^HOOKS\./p) { 27 | next if (/^HOOKS\..+\.file$/p); 28 | $val = ::trim($::Fields{$_}) // ''; 29 | $key = ${^POSTMATCH}; 30 | $hooks .= "[$key]=\"$val\"\n"; 31 | } 32 | } 33 | 34 | $hooks .= ")\n"; 35 | 36 | my $fh; 37 | my $File = $::Fields{"HOOKS.$p[0].file"} // ''; 38 | 39 | open($fh, '>', $File) or die "Can't open file \"$File\""; 40 | { 41 | local $/; 42 | print $fh "$hooks"; 43 | } 44 | close($fh); 45 | 46 | print "Hooks saved to file \"$File\"\n"; 47 | 48 | return; 49 | } 50 | 51 | 1; 52 | -------------------------------------------------------------------------------- /VX/schema.d/action.multipath/wwid.pm: -------------------------------------------------------------------------------- 1 | package action::wwid; 2 | # HW template [WWID xx] dongle 3 | 4 | use strict; 5 | use warnings; 6 | use Exporter; 7 | 8 | our @ISA= qw( Exporter ); 9 | 10 | # these CAN be exported. 11 | our @EXPORT_OK = qw( WWID ); 12 | 13 | # these are exported by default. 14 | our @EXPORT = qw( WWID ); 15 | 16 | sub WWID { 17 | $_ = shift; # Params array reference 18 | my $descr = shift // ''; 19 | 20 | my @p = @{$_}; 21 | 22 | my $host = $::Fields{"WWID.$p[0].host"} // ''; 23 | # print "HOST=$host\n"; 24 | return if (($host ne '') && ("$host\n" ne `/bin/hostname`)); 25 | 26 | $descr =~ s/^[\s]*;[\s]*//; 27 | 28 | # print "WWID.$p[0].name = $descr\n"; 29 | 30 | my $alias = $::Fields{"WWID.$p[0].alias"} // ''; 31 | $alias =~ s/[^0-9A-Za-z_.-]//g unless ($alias eq '!'); 32 | 33 | if ($alias ne '') { 34 | my $vm = $::Fields{"WWID.$p[0].vm"} // ''; 35 | my $wwid = '3' . $p[0]; 36 | $wwid =~ s/://g; 37 | $wwid =~ s/[^0-9A-Fa-f]//g; 38 | $wwid = lc($wwid); 39 | $wwid =~ s/^3// if ((length($wwid) == 34) && ($wwid =~ /^33/)); 40 | 41 | if ($alias eq '!' && length($wwid) == 33) { 42 | $alias = `/sbin/multipath -ll $wwid | /bin/grep $wwid | /usr/bin/awk '{print \$1}'`; 43 | $alias =~ s/[ ]*\n$//; 44 | $::Fields{"MULTIPATH.destroy"}{"$wwid"} = "$alias"; 45 | return; 46 | ####### 47 | 48 | print "** Removing wwid=$wwid\n"; 49 | print `/sbin/multipath -ll $wwid`; 50 | `/sbin/multipath -ll $wwid | /bin/grep -q $wwid`; 51 | if (($? >> 8) == 0) { 52 | print "Deleting devices for $wwid alias \"$alias\"\n"; 53 | `/sbin/multipath -ll $wwid | /bin/egrep -o ' sd[a-z0-9]+ ' | /bin/sed 's/ //g' | /usr/bin/awk '{print "echo 1 > /sys/block/"\$1"/device/delete"}' | /bin/sh`; 54 | 55 | print `/sbin/dmsetup info "$alias"`; 56 | if (($? >> 8) == 0) { 57 | print "Sending destroying message for $alias\n"; 58 | `/sbin/dmsetup message "$alias" 0 "fail_if_no_path"`; 59 | `/sbin/dmsetup info "$alias"`; 60 | ### Calling "dmsetup message" looks safer ### 61 | # print "Calling dmsetup remove $alias\n"; 62 | # `/sbin/dmsetup remove -f "$alias"`; 63 | ### Moved to hooks ### 64 | # `for i in \$(/usr/bin/systool -c fc_host | /bin/grep 'Class Device' | /bin/egrep -o 'host[0-9]+') ; do echo '- - -' > \$(/usr/bin/find /sys/devices/ -name 'scan' | /bin/grep "/\$i/") ; done`; 65 | # `/sbin/multipathd reconfigure ; /usr/sbin/service multipath-tools start`; 66 | } 67 | } 68 | } else { 69 | $wwid .= ' ### INVALID LENGTH ###' if (length($wwid) != 33); 70 | my $multipath = " multipath {\n wwid $wwid\n alias $alias\n }\n"; 71 | $::Fields{"MULTIPATH.multipaths"} .= $multipath; 72 | $::Fields{"HOOKS.$vm"} .= " $alias" if ($vm ne ''); 73 | } 74 | } 75 | return; 76 | } 77 | 78 | 1; 79 | -------------------------------------------------------------------------------- /VX/schema.d/action/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Pluggable modules for VX block-schema language translator 2 | Copyright (C) 2014-2018 Dmitry Svyatogorov ds@vo-ix.ru 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU Affero General Public License as 6 | published by the Free Software Foundation, either version 3 of the 7 | License, or (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | 14 | You should have received a copy of the GNU Affero General Public License 15 | along with this program. If not, see . 16 | -------------------------------------------------------------------------------- /VX/schema.d/action/action.pm: -------------------------------------------------------------------------------- 1 | action::action; 2 | use strict; 3 | use warnings; 4 | use Exporter; 5 | 6 | our @ISA= qw( Exporter ); 7 | 8 | # these CAN be exported. 9 | our @EXPORT_OK = qw( action ACTION ); 10 | 11 | # these are exported by default. 12 | our @EXPORT = qw( action ACTION ); 13 | 14 | sub action { 15 | my $_ = shift; # Params array reference 16 | my $descr = shift // ''; 17 | 18 | my @p = @{$_}; 19 | my $fn = $p[0] // ''; 20 | $fn = $::LABELS{"ACTION.$fn"}; 21 | 22 | print "[Func ". ::FN() ."] $descr\n"; 23 | if (defined $fn) { 24 | print " Macro = Switch ; (fn,Var,Limit,FallBack)\n"; 25 | print " P1 = Func($fn)\n"; 26 | if (defined $::Fields{"ROOT.CallLimit"} && $::Fields{"ROOT.CallLimit"} =~ /^\d+$/) { 27 | print " P3 = " . $::Fields{"ROOT.CallLimit"} . "\n"; 28 | delete $::Fields{"ROOT.CallLimit"}; 29 | } 30 | } else { 31 | print "; Action not defined!"; 32 | print " Macro = Hang ; Terminate call\n"; 33 | } 34 | 35 | print "\n"; 36 | return undef; 37 | } 38 | 39 | sub ACTION { 40 | return; 41 | } 42 | 43 | 1; 44 | -------------------------------------------------------------------------------- /VX/schema.d/action/alias.pm: -------------------------------------------------------------------------------- 1 | package action::alias; 2 | # Switch to Func($alias) 3 | 4 | use strict; 5 | use warnings; 6 | use Exporter; 7 | 8 | our @ISA= qw( Exporter ); 9 | 10 | # these CAN be exported. 11 | our @EXPORT_OK = qw( alias ); 12 | 13 | # these are exported by default. 14 | our @EXPORT = qw( alias ); 15 | 16 | sub alias { 17 | my $_ = shift; # Params array reference 18 | my $descr = shift // ''; 19 | 20 | my @p = @{$_}; 21 | my $alias = $p[0]; 22 | 23 | print "[Func ". ::FN() ."] $descr\n"; 24 | if (defined $alias) { 25 | print " Macro = Switch ; (fn,Var,Limit,FallBack)\n"; 26 | print " P1 = Func($alias)\n"; 27 | } else { 28 | print "; Func not defined!"; 29 | print " Macro = Hang ; Terminate call\n"; 30 | } 31 | 32 | print "\n"; 33 | return undef; 34 | } 35 | -------------------------------------------------------------------------------- /VX/schema.d/action/ext.pm: -------------------------------------------------------------------------------- 1 | package action::ext; 2 | # TABLE "Exten" 3 | 4 | use strict; 5 | use warnings; 6 | use Exporter; 7 | 8 | our @ISA= qw( Exporter ); 9 | 10 | # these CAN be exported. 11 | our @EXPORT_OK = qw( ext EXT ); 12 | 13 | # these are exported by default. 14 | our @EXPORT = qw( ext EXT ); 15 | 16 | sub ext { 17 | my $_ = shift; # Params array reference 18 | my $descr = shift // ''; 19 | 20 | my @p = @{$_}; 21 | my $ext = $p[0]; 22 | 23 | print "[Func ". ::FN() ."] $descr\n"; 24 | if (defined $ext) { 25 | print " Macro = Switch ; (fn,Var,Limit,FallBack)\n"; 26 | print " P1 = Exten($ext)\n"; 27 | if (defined $::Fields{"ROOT.calllimit"} && $::Fields{"ROOT.calllimit"} =~ /^\d+$/) { 28 | print " P3 = " . $::Fields{"ROOT.calllimit"} . "\n"; 29 | delete $::Fields{"ROOT.calllimit"}; 30 | } 31 | } else { 32 | print "; Exten not defined!"; 33 | print " Macro = Hang ; Terminate call\n"; 34 | } 35 | 36 | print "\n"; 37 | return undef; 38 | } 39 | 40 | sub EXT { # Declare extension 41 | my $_ = shift; # Params array reference 42 | my $descr = shift // ''; 43 | 44 | my @p = @{$_}; 45 | my $ext = $p[0]; 46 | 47 | # my @act = (); 48 | # my $i = 0; 49 | 50 | print "[Exten $ext]$descr\n"; 51 | 52 | for (sort keys %::Fields) { 53 | if (/^EXT\.$::LABEL\./p) { 54 | my $val = $::Fields{$_}; 55 | $val //= ''; 56 | my $key = ${^POSTMATCH}; 57 | 58 | next if ($key =~/^transfer/); 59 | # if ($key =~/^Transfer/) { 60 | # my @k = (); 61 | # ::Keys($::Fields{$_},\@k); 62 | # 63 | # if ($k[0] eq 'ext') { 64 | # $val = $k[1]; 65 | # } else { 66 | # push(@act, $val); 67 | # 68 | # $val = ::FN($i); 69 | # $i++; 70 | # } 71 | # } 72 | 73 | ### print " $key = $val\n"; 74 | print " Timeout = $val\n" if ("$key" eq 'timeout'); 75 | } 76 | } 77 | 78 | # OnBusy & OnTimeout actions 79 | my @act = (); 80 | my @lb = (); 81 | 82 | my $TransferOnBusy = $::Fields{"EXT.$::LABEL.transferonbusy"}; 83 | my $TransferOnTimeout = $::Fields{"EXT.$::LABEL.transferontimeout"}; 84 | my $TransferCall = $::Fields{"EXT.$::LABEL.transfercall"}; 85 | my $SpawnCalls = $::Fields{"EXT.$::LABEL.spawncalls"}; 86 | my @k = (); 87 | my $val; 88 | 89 | if (defined $TransferOnBusy) { 90 | ::Keys($TransferOnBusy,\@k); 91 | if ($k[0] eq 'ext') { 92 | $val = $k[1] // ''; 93 | } else { # Take current extension label, add mnemonic suffix 94 | $::LABELS{"EXT.transferonbusy"} = ::FN() . '.B'; # Rewrite! 95 | push(@act, $TransferOnBusy); 96 | push(@lb, "transferonbusy"); 97 | $val = $::LABELS{"EXT.transferonbusy"}; 98 | } 99 | print " TransferOnBusy = $val\n"; 100 | } 101 | if (defined $TransferOnTimeout) { 102 | ::Keys($TransferOnTimeout,\@k); 103 | if ($k[0] eq 'ext') { 104 | $val = $k[1] // ''; 105 | } else { 106 | $::LABELS{"EXT.transferontimeout"} = ::FN() . '.T'; # Rewrite! 107 | push(@act, $TransferOnTimeout); 108 | push(@lb, "transferontimeout"); 109 | $val = $::LABELS{"EXT.transferontimeout"}; 110 | } 111 | print " TransferOnTimeout = $val\n"; 112 | } 113 | if (defined $TransferCall) { 114 | ::Keys($TransferCall,\@k); 115 | if ($k[0] eq 'ext') { 116 | $val = $k[1] // ''; 117 | } else { 118 | $::LABELS{"EXT.transfercall"} = ::FN() . '.X'; # Rewrite! 119 | push(@act, $TransferCall); 120 | push(@lb, "transfercall"); 121 | $val = $::LABELS{"EXT.transfercall"}; 122 | } 123 | print " TransferCall = $val\n"; 124 | } 125 | if (defined $SpawnCalls) { # No "SpawnCalls" object really defined at this time!!! 126 | ::Keys($SpawnCalls,\@k); 127 | if ($k[0] eq 'ext') { 128 | $val = $k[1] // ''; 129 | } else { 130 | $::LABELS{"EXT.spawncalls"} = ::FN() . '.X'; # Rewrite! 131 | push(@act, $SpawnCalls); 132 | push(@lb, "spawncalls"); 133 | $val = $::LABELS{"EXT.spawncalls"}; 134 | } 135 | print " SpawnCalls = $val\n"; 136 | } 137 | 138 | print "\n"; 139 | 140 | my $i = 0; 141 | for (@act) { 142 | $::LABEL = $lb[$i++]; # Still need the label hack :/ 143 | $::oi = -1; 144 | ::Action($_); 145 | } 146 | 147 | return; 148 | } 149 | 150 | 1; 151 | -------------------------------------------------------------------------------- /VX/schema.d/action/fax.pm: -------------------------------------------------------------------------------- 1 | action::fax; 2 | # macro fax(mailbox, id, header,tiff) 3 | 4 | use strict; 5 | use warnings; 6 | use Exporter; 7 | 8 | our @ISA= qw( Exporter ); 9 | 10 | # these CAN be exported. 11 | our @EXPORT_OK = qw( fax ); 12 | 13 | # these are exported by default. 14 | our @EXPORT = qw( fax ); 15 | 16 | sub fax { 17 | my $_ = shift; # Params array reference 18 | my $descr = shift // ''; 19 | 20 | my @p = @{$_}; 21 | 22 | print "[Func ". ::FN() ."] $descr\n"; 23 | print " Macro = fax ; (mailbox,id,header,tiff)\n"; 24 | 25 | my $mailbox = $p[0] // $::Fields{'ROOT.Admin'}; 26 | if (defined $mailbox) { 27 | print " P1 = \'$mailbox\'\n"; 28 | } else { 29 | print "; Mailbox not defined!\n"; 30 | } 31 | 32 | my $id = $p[1]; 33 | my $header = $p[2]; 34 | my $tiff = $p[3]; 35 | 36 | print " P2 = $id\n" if defined $id; 37 | print " P3 = $header\n" if defined $header; 38 | print " P4 = $tiff\n" if defined $tiff; 39 | 40 | print "\n"; 41 | return undef; 42 | } 43 | 44 | 1; 45 | -------------------------------------------------------------------------------- /VX/schema.d/action/fnc.pm: -------------------------------------------------------------------------------- 1 | package action::fnc; 2 | # Switch to Func($fnc) 3 | 4 | use strict; 5 | use warnings; 6 | use Exporter; 7 | 8 | our @ISA= qw( Exporter ); 9 | 10 | # these CAN be exported. 11 | our @EXPORT_OK = qw( fnc ); 12 | 13 | # these are exported by default. 14 | our @EXPORT = qw( fnc ); 15 | 16 | sub fnc { 17 | my $_ = shift; # Params array reference 18 | my $descr = shift // ''; 19 | 20 | my @p = @{$_}; 21 | my $fnc = $p[0]; 22 | 23 | print "[Func ". ::FN() ."] $descr\n"; 24 | if (defined $fnc) { 25 | print " Macro = Switch ; (fn,Var,Limit,FallBack)\n"; 26 | print " P1 = Func($fnc)\n"; 27 | if (defined $::Fields{"ROOT.CallLimit"} && $::Fields{"ROOT.CallLimit"} =~ /^\d+$/) { 28 | print " P3 = " . $::Fields{"ROOT.CallLimit"} . "\n"; 29 | delete $::Fields{"ROOT.CallLimit"}; 30 | } 31 | } else { 32 | print "; Func not defined!"; 33 | print " Macro = Hang ; Terminate call\n"; 34 | } 35 | 36 | print "\n"; 37 | return undef; 38 | } 39 | -------------------------------------------------------------------------------- /VX/schema.d/action/hangup.pm: -------------------------------------------------------------------------------- 1 | package action::hangup; 2 | # macro Hang() 3 | 4 | use strict; 5 | use warnings; 6 | use Exporter; 7 | 8 | our @ISA= qw( Exporter ); 9 | 10 | # these CAN be exported. 11 | our @EXPORT_OK = qw( hangup ); 12 | 13 | # these are exported by default. 14 | our @EXPORT = qw( hangup ); 15 | 16 | sub hangup { 17 | my $_ = shift; # Params array reference 18 | my $descr = shift // ''; 19 | 20 | my @p = @{$_}; 21 | 22 | print "[Func ". ::FN() ."] $descr\n"; 23 | print " Macro = Hang ; Terminate call\n"; 24 | 25 | print "\n"; 26 | return undef; 27 | } 28 | 29 | 1; 30 | -------------------------------------------------------------------------------- /VX/schema.d/action/hw.pm: -------------------------------------------------------------------------------- 1 | package action::hw; 2 | # HW template processor for various ip phones/gates 3 | 4 | use strict; 5 | use warnings; 6 | use Exporter; 7 | 8 | use Cwd qw(abs_path); 9 | 10 | our @ISA= qw( Exporter ); 11 | 12 | # these CAN be exported. 13 | our @EXPORT_OK = qw( HW ); 14 | 15 | # these are exported by default. 16 | our @EXPORT = qw( HW ); 17 | 18 | sub HW { 19 | # return if $::OBJECTS{"HW.$::LABEL"}; 20 | my $_ = shift; # Params array reference 21 | my $descr = shift // ''; 22 | 23 | use Switch 'Perl6'; 24 | 25 | my @p = @{$_}; 26 | my $hw = lc($p[0]); 27 | 28 | # Config values 29 | my $Enable1 = $::Fields{"LINE.1.enable"} // $::Fields{'ROOT.enable'} // '0'; 30 | $Enable1 = ($Enable1 ? 'Yes':'No'); 31 | my $Enable2 = $::Fields{"LINE.2.enable"} // $::Fields{'ROOT.enable'} // '0'; 32 | $Enable2 = ($Enable2 ? 'Yes':'No'); 33 | 34 | my $Host = $::Fields{'ROOT.host'} // ''; 35 | my $Host1 = $::Fields{"LINE.1.host"} // $Host; 36 | my $Host2 = $::Fields{"LINE.2.host"} // $Host; 37 | 38 | my $DialPlan1 = $::Fields{"LINE.1.dialplan"} // $::Fields{'ROOT.dialplan'} // 'x.'; 39 | $DialPlan1 = ::unquote(::trim($DialPlan1)); 40 | $DialPlan1 =~ s//>/; 42 | my $DialPlan2 = $::Fields{"LINE.2.dialplan"} // $::Fields{'ROOT.dialplan'} // 'x.'; 43 | $DialPlan2 = ::unquote(::trim($DialPlan2)); 44 | $DialPlan2 =~ s//>/; 46 | 47 | my $Name1 = $::Fields{"LINE.1.name"} // ''; 48 | my $Name2 = $::Fields{"LINE.2.name"} // ''; 49 | 50 | my $User1 = $::Fields{"LINE.1.user"} // ''; 51 | $User1 =~ s/(?; 79 | } 80 | close($fh); 81 | 82 | $content =~ s/#NTP#/$Host/g; 83 | $content =~ s/#LOG#/$Host/g; 84 | $content =~ s/#DEBUG#/$Host/g; 85 | 86 | $content =~ s/#ENABLE1#/$Enable1/g; 87 | $content =~ s/#ENABLE2#/$Enable2/g; 88 | 89 | $content =~ s/#MOH1#/$Host1/g; 90 | $content =~ s/#MOH2#/$Host2/g; 91 | 92 | $content =~ s/#PROXY1#/$Host1/g; 93 | $content =~ s/#PROXY2#/$Host2/g; 94 | 95 | $content =~ s/#NAME1#/$Name1/g; 96 | $content =~ s/#NAME2#/$Name2/g; 97 | 98 | $content =~ s/#USER1#/$User1/g; 99 | $content =~ s/#USER2#/$User2/g; 100 | 101 | $content =~ s/#SECRET1#/$Secret1/g; 102 | $content =~ s/#SECRET2#/$Secret2/g; 103 | 104 | $content =~ s/#DIALPLAN1#/$DialPlan1/g; 105 | $content =~ s/#DIALPLAN2#/$DialPlan2/g; 106 | 107 | # print $content; 108 | # Output 109 | my $n = $::DID; 110 | if ($n > 0) { 111 | $n = ".$n"; 112 | } else { 113 | $n = ''; 114 | } 115 | $n = "$::BIND$n"; 116 | 117 | my $File = $::Fields{"HW.$p[0].file"} // ''; 118 | $File =~ s/(?', $File) or die "Can't open file \"$File\""; 131 | { 132 | local $/; 133 | print $fh "$content"; 134 | } 135 | close($fh); 136 | 137 | print "Config saved to file \"$File\"\n"; 138 | # $::OBJECTS{"HW.$::LABEL"} = 1; 139 | 140 | # Jerk device, if any 141 | #http://192.168.5.46/admin/resync?http://a3.ptf.spb.ru/spa_4882.1 142 | my $ip = $::Fields{"HW.$p[0].ip"} // ''; 143 | return unless $ip; 144 | if ($ip !~ /\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}\b/) { # IPv4 145 | print STDERR "Invalid IPv4: \"$ip\"\n"; 146 | exit 1; 147 | } 148 | 149 | my $url; 150 | given ($hw) { 151 | when "spa2102" { 152 | $url = "http://$ip/admin/resync?http://$Host/$conf"; 153 | } 154 | default { 155 | print STDERR "HW unknown: \"$hw\"\n"; 156 | exit 1; 157 | } 158 | } 159 | 160 | print "CURL $url\n\033\[1;34m"; 161 | print `/usr/bin/curl --silent --show-error --connect-timeout 3 --max-time 3 '$url'`; 162 | print "\033\[0m"; 163 | 164 | return; 165 | } 166 | 167 | 1; 168 | -------------------------------------------------------------------------------- /VX/schema.d/action/indication.pm: -------------------------------------------------------------------------------- 1 | package action::indication; 2 | # macro Indication(fn,Timeout,NoTransfer,Limit) 3 | 4 | use strict; 5 | use warnings; 6 | use Exporter; 7 | 8 | our @ISA= qw( Exporter ); 9 | 10 | # these CAN be exported. 11 | our @EXPORT_OK = qw( indication ); 12 | 13 | # these are exported by default. 14 | our @EXPORT = qw( indication ); 15 | 16 | sub indication { 17 | my $_ = shift; # Params array reference 18 | my $descr = shift // ''; 19 | 20 | my @p = @{$_}; 21 | 22 | print "[Func ". ::FN() ."] $descr\n"; 23 | print " Macro = Indication ; (fn,Timeout,NoTransfer,Limit)\n"; 24 | 25 | my $Timeout = $p[0]; 26 | my $Limit = $p[1]; 27 | my $NoTransfer = $p[2]; 28 | 29 | if ($::oi >= 0) { 30 | print " P1 = Func(". ::FN(1) .")\n"; 31 | } else { 32 | print " P1 = Hang()\n"; # Break chain for special objects 33 | } 34 | 35 | print " P2 = $Timeout\n" if defined $Timeout; 36 | print " P3 = $Limit\n" if defined $Limit; 37 | print " P4 = $NoTransfer\n" if defined $NoTransfer; 38 | 39 | print "\n"; 40 | return "hangup"; 41 | } 42 | 43 | 1; 44 | -------------------------------------------------------------------------------- /VX/schema.d/action/level.pm: -------------------------------------------------------------------------------- 1 | package action::level; 2 | # macro CallLevelI(F_gt,F_le,level) 3 | 4 | use strict; 5 | use warnings; 6 | use Exporter; 7 | 8 | our @ISA= qw( Exporter ); 9 | 10 | # these CAN be exported. 11 | our @EXPORT_OK = qw( level LEVEL ); 12 | 13 | # these are exported by default. 14 | our @EXPORT = qw( level LEVEL ); 15 | 16 | sub level { 17 | my $_ = shift; # Params array reference 18 | my $descr = shift // ''; 19 | my $inline = shift // 0; 20 | 21 | my @p = @{$_}; 22 | 23 | print "[Func ". ::FN() ."] $descr\n"; 24 | print " Macro = CallLevelI ; (F_gt,F_le,level)\n"; 25 | 26 | my $fn = $p[0] // ''; 27 | ::ON("LEVEL","$fn"); 28 | $fn = $::LABELS{"LEVEL.$fn"}; 29 | if (defined $fn) { 30 | $fn = "Func($fn)"; 31 | } else { 32 | $fn = 'Hang() ; No action for CallLevelI!'; 33 | } 34 | 35 | print " P1 = $fn\n"; 36 | print " P2 = Func(". ::FN(1) .")\n" if $::oi >= 0; # Break chain for special objects 37 | print " P3 = \'$p[1]\'\n" if $p[1]; 38 | 39 | print "\n"; 40 | return 'hangup'; 41 | } 42 | 43 | sub LEVEL { 44 | return; 45 | } 46 | 47 | 1; 48 | -------------------------------------------------------------------------------- /VX/schema.d/action/line.pm: -------------------------------------------------------------------------------- 1 | package action::line; 2 | # HW template [LINE xx] dongle 3 | 4 | use strict; 5 | use warnings; 6 | use Exporter; 7 | 8 | our @ISA= qw( Exporter ); 9 | 10 | # these CAN be exported. 11 | our @EXPORT_OK = qw( LINE ); 12 | 13 | # these are exported by default. 14 | our @EXPORT = qw( LINE ); 15 | 16 | sub LINE { 17 | my $_ = shift; # Params array reference 18 | my $descr = shift // ''; 19 | 20 | my @p = @{$_}; 21 | 22 | $descr =~ s/^[\s]*;[\s]*//; 23 | $::Fields{"LINE.$p[0].name"} = $descr; 24 | return; 25 | } 26 | 27 | 1; 28 | -------------------------------------------------------------------------------- /VX/schema.d/action/menu.pm: -------------------------------------------------------------------------------- 1 | action::menu; 2 | # macro Menu(NRec,NoHang) 3 | 4 | use strict; 5 | use warnings; 6 | use Exporter; 7 | 8 | our @ISA= qw( Exporter ); 9 | 10 | # these CAN be exported. 11 | our @EXPORT_OK = qw( menu MENU ); 12 | 13 | # these are exported by default. 14 | our @EXPORT = qw( menu MENU ); 15 | 16 | sub menu { 17 | my $_ = shift; # Params array reference 18 | my $descr = shift // ''; 19 | 20 | my @p = @{$_}; 21 | 22 | print "[Func ". ::FN() ."] $descr\n"; 23 | print " Macro = Menu ; (NRec,NoHang,Limit)\n"; 24 | 25 | print " P1 = \'" . ::FL('MENU',join(' ',@p),1) . "\'\n"; 26 | if (defined $::Fields{"ROOT.calllimit"} && $::Fields{"ROOT.calllimit"} =~ /^\d+$/) { 27 | print " P3 = " . $::Fields{"ROOT.calllimit"} . "\n"; 28 | delete $::Fields{"ROOT.calllimit"}; 29 | } 30 | 31 | print "\n"; 32 | return undef; 33 | } 34 | 35 | sub MENU { # Declare menu 36 | my $_ = shift; # Params array reference 37 | my $descr = shift // ''; 38 | 39 | my @p = @{$_}; 40 | my %ACT = (); # Menu on-key actions 41 | my @act = (); 42 | my @lb = (); 43 | my ($key, $val); 44 | 45 | print "[Menu " . ::FL('MENU',join(' ',@p),1) ."]$descr\n"; 46 | 47 | for (sort keys %::Fields) { 48 | if (/^MENU\.$::LABEL\./p) { 49 | $val = ::unquote($::Fields{$_}) // ''; 50 | $key = ${^POSTMATCH}; 51 | 52 | if ($key =~/^[(]?[\s'"]*([0-9*#]|timeoutaction)[\s'",]*/ && $val !~ '^BACK:?') { 53 | my $l = $1; 54 | my @k = (); 55 | &Keys($::Fields{$_},\@k); 56 | 57 | if ($k[0] eq 'ext') { 58 | $val = "Exten($k[1])"; 59 | } else { 60 | $::LABELS{"MENU.$key"} = ::FN('MENU',$::LABEL) . '.' . substr($1,0,1); # Rewrite! 61 | push(@act, $val); 62 | push(@lb, $key); 63 | $val = "Func(" . $::LABELS{"MENU.$key"} . ")"; 64 | } 65 | $key = '"' . $key . '"' unless ($key eq 'timeoutaction') || $key =~ /^\(/; 66 | ${key} = 'TimeoutAction' if ($key eq 'timeoutaction'); 67 | $ACT{$key} = $val; 68 | } else { 69 | if ($key =~ /hello|prompt/) { 70 | $val = "$::BIND/$val" if ($val ne ''); 71 | } 72 | if ($key eq 'parent') { 73 | $val = ::FL('MENU',$val,1); 74 | } 75 | $key = uc(substr($key,0,1)) . substr($key,1); 76 | print "\"$key\" = '$val'\n"; 77 | } 78 | } 79 | } 80 | 81 | for (sort keys %ACT) { 82 | print " $_ = $ACT{$_}\n"; 83 | } 84 | 85 | print "\n"; 86 | 87 | my $i = 0; 88 | for (@act) { 89 | $::LABEL = $lb[$i++]; # Still need the label hack :/ 90 | $::oi = -1; 91 | 92 | Action($_); 93 | } 94 | return; 95 | } 96 | 97 | 1; 98 | -------------------------------------------------------------------------------- /VX/schema.d/action/playback.pm: -------------------------------------------------------------------------------- 1 | package action::playback; 2 | # macro PlayFiles(File,Count,NoAnswer,fn) 3 | 4 | use strict; 5 | use warnings; 6 | use Exporter; 7 | 8 | our @ISA= qw( Exporter ); 9 | 10 | # these CAN be exported. 11 | our @EXPORT_OK = qw( playback ); 12 | 13 | # these are exported by default. 14 | our @EXPORT = qw( playback ); 15 | 16 | sub playback { 17 | my $_ = shift; # Params array reference 18 | my $descr = shift // ''; 19 | 20 | my @p = @{$_}; 21 | 22 | my $bind = ''; 23 | $bind = "$::BIND/" if ($::BIND ne '' && uc($::BIND) ne 'NULL'); 24 | 25 | print "[Func ". ::FN() ."] $descr\n"; 26 | print " Macro = PlayFiles ; (File,Count,NoAnswer,fn)\n"; 27 | 28 | my $File = $p[0] // '\'silence/1\''; 29 | $File = "\'$bind" . ::unquote($p[0])."\'" if defined $p[0]; 30 | 31 | my $Count = $p[1]; 32 | my $NoAnswer = $p[2]; 33 | 34 | print " P1 = $File\n"; 35 | print " P2 = $Count\n" if defined $Count; 36 | print " P3 = $NoAnswer\n" if defined $NoAnswer; 37 | print " P4 = Func(". ::FN(1) .")\n" if $::oi >= 0; # Break chain for special objects 38 | 39 | print "\n"; 40 | return "hangup"; 41 | } 42 | 43 | 1; 44 | -------------------------------------------------------------------------------- /VX/schema.d/action/schedule.pm: -------------------------------------------------------------------------------- 1 | package action::schedule; 2 | # macro Schedule(Spec,f1,f2,ZT) 3 | 4 | use strict; 5 | use warnings; 6 | use Exporter; 7 | 8 | our @ISA= qw( Exporter ); 9 | 10 | # these CAN be exported. 11 | our @EXPORT_OK = qw( schedule SCHEDULE ); 12 | 13 | # these are exported by default. 14 | our @EXPORT = qw( schedule SCHEDULE ); 15 | 16 | sub schedule { 17 | my $_ = shift; # Params array reference 18 | my $descr = shift // ''; 19 | my $inline = shift // 0; 20 | 21 | my @p = @{$_}; 22 | 23 | print "[Func ". ::FN() ."] $descr\n"; 24 | print " Macro = Schedule ; (Spec,f1,f2,ZT)\n"; 25 | 26 | my $fn = $p[0] // ''; 27 | $fn = $::LABELS{"SCHEDULE.$fn"}; 28 | if (defined $fn) { 29 | $fn = "Func($fn)"; 30 | } else { 31 | $fn = 'Hang() ; No action on this schedule!'; 32 | } 33 | 34 | if ($inline) { 35 | print " P1 = \'$p[0]\'\n"; 36 | } else { 37 | print " P1 = \'" . ::FL('SCHEDULE',join(' ',@p),1) . "\'\n"; 38 | } 39 | print " P2 = $fn\n"; 40 | print " P3 = Func(". ::FN(1) .")\n" if $::oi >= 0; # Break chain for special objects 41 | 42 | print "\n"; 43 | return 'hangup'; 44 | } 45 | 46 | sub SCHEDULE { 47 | my $_ = shift; # Params array reference 48 | my $descr = shift // ''; 49 | 50 | my @p = @{$_}; 51 | 52 | return if (defined $p[1] && $p[1] eq '='); 53 | 54 | print "[Schedule " . ::FL('SCHEDULE',join(' ',@p),1) ."]$descr\n"; 55 | print " NOT = 1 ; Inverse match\n" if $p[1]; 56 | 57 | my $s = 'Schedules'; 58 | for (sort keys %::Fields) { 59 | if (/^SCHEDULE\.$::LABEL\./) { 60 | print "[$s]\n"; 61 | $s = ''; 62 | 63 | my @sched = split(/(?<=[\w\d`'"*])[\s]+(?=[\w\d`'"*])|[\s]*[^\w\d\s`'"*:.-][\s]*/, $::Fields{$_}); 64 | # print "*0*@sched[0]*1*@sched[1]*2*@sched[2]*3*@sched[3]*4*@sched[4]***\n"; 65 | my $not = 0; 66 | 67 | if ($sched[0] eq '!') { 68 | shift @sched; 69 | $not = 1; 70 | } 71 | if ($sched[0] =~ /^!/) { 72 | $sched[0] =~ s/^.//; 73 | $not = 1; 74 | } 75 | print "NOT = 1 ; Exclusion\n" if (/\!$/ || $not); 76 | # A bit of heuristic about what's written 77 | # if (defined $sched[1] && $sched[1] =~ /^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i) { # Day+Month instead of time 78 | # unshift(@sched,undef); unshift(@sched,undef); 79 | # } 80 | 81 | if (defined $sched[0]) { 82 | if ($sched[0] =~ /^(sun|mon|tue|wed|thu|fri|sat)/i) { # DoW instead of time 83 | unshift(@sched,undef); 84 | } elsif ($sched[0] =~/^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)|^\d{4}/i) { # Month|Year instead of time 85 | unshift(@sched,undef); unshift(@sched,undef); unshift(@sched,undef); 86 | unshift(@sched,undef) if $sched[3] =~/^\d{4}[^\d:]/; 87 | } elsif ($sched[0] =~ /^\d{1,2}[^\d:]/) { # DoM instead of time 88 | unshift(@sched,undef); unshift(@sched,undef); 89 | } 90 | } 91 | 92 | if (defined $sched[0]) { 93 | $sched[0] =~ s/\.\./-/g; 94 | $sched[0] =~ s/[^0-9:-]//g; 95 | print " TimeRange = $sched[0]\n"; 96 | } 97 | if (defined $sched[1]) { 98 | $sched[1] = lc($sched[1]); 99 | $sched[1] =~ s/\.\./-/g; 100 | $sched[1] =~ s/[^\w-]//g; 101 | print " DaysOfWeek = $sched[1]\n"; 102 | } 103 | if (defined $sched[2]) { 104 | $sched[2] =~ s/\.\./-/g; 105 | $sched[2] =~ s/[^0-9-]//g; 106 | print " DaysOfMonth = $sched[2]\n"; 107 | } 108 | if (defined $sched[3]) { 109 | $sched[3] = lc($sched[3]); 110 | $sched[3] =~ s/\.\./-/g; 111 | $sched[3] =~ s/[^\w-]//g; 112 | print " Month = $sched[3]\n"; 113 | } 114 | if (defined $sched[4]) { 115 | $sched[4] =~ s/\.\./-/g; 116 | $sched[4] =~ s/[^0-9-]//g; 117 | print " Year = $sched[4]\n"; 118 | } 119 | } 120 | } 121 | print "; This schedule has no conditions!\n" if $s; 122 | 123 | print "\n"; 124 | return; 125 | } 126 | 127 | 1; 128 | -------------------------------------------------------------------------------- /VX/schema.d/action/vm.pm: -------------------------------------------------------------------------------- 1 | action::vm; 2 | ## macro VM(MailBox,MailContext,flags,fn) 3 | # macro VMail(Mail,MaxLength,Prompt,fn) 4 | 5 | use strict; 6 | use warnings; 7 | use Exporter; 8 | 9 | our @ISA= qw( Exporter ); 10 | 11 | # these CAN be exported. 12 | our @EXPORT_OK = qw( vm ); 13 | 14 | # these are exported by default. 15 | our @EXPORT = qw( vm ); 16 | 17 | sub vm { 18 | my $_ = shift; # Params array reference 19 | my $descr = shift // ''; 20 | 21 | my @p = @{$_}; 22 | 23 | my $bind = ''; 24 | $bind = "$::BIND/" if ($::BIND ne '' && uc($::BIND) ne 'NULL'); 25 | 26 | print "[Func ". ::FN() ."] $descr\n"; 27 | print " Macro = VMail ; (Mail,MaxLength,Prompt,fn)\n"; 28 | 29 | my $MailBox = $p[0] // $::Fields{'ROOT.admin'}; 30 | if (defined $MailBox) { 31 | $MailBox =~ s/\,/\%/g; 32 | print " P1 = \'" . ::unquote($MailBox) . "\'\n"; 33 | } else { 34 | print "; Mailbox not defined!\n"; 35 | } 36 | 37 | my $MaxLength = $p[1]; 38 | my $Prompt = $p[2]; 39 | 40 | print " P2 = " . ::unquote($MaxLength) . "\n" if defined $MaxLength; 41 | if (defined $Prompt) { 42 | if (::unquote($Prompt) ne ':') { 43 | print " P3 = $bind" . ::unquote($Prompt) . "\n"; 44 | } else { 45 | print " P3 = " . ::unquote($Prompt) . "\n"; 46 | } 47 | } 48 | print " P4 = Func(". ::FN(1) .")\n" if $::oi >= 0; # Break chain for special objects 49 | 50 | print "\n"; 51 | return 'hangup'; 52 | } 53 | 54 | #[voicemail ip#gdberg.ru] ; VoiceMail for Gardenberg 55 | # email = ip@gdberg.ru 56 | # serveremail = Gardenberg.PBX 57 | # delete = yes ; Delete record after message sent 58 | # password = Amm6PqZBmDrd 59 | # fullname = "Mailbox $$" 60 | # 61 | #[Func VM] ; VoiceMail for Gardenberg 62 | # Macro = VM ; macro VM(MailBox,MailContext,flags,fn) 63 | # P1 = 'ip#gdberg.ru' 64 | # P4 = Hang(16) 65 | 66 | sub VM { 67 | return; 68 | } 69 | 1; 70 | -------------------------------------------------------------------------------- /x switcher/README.md: -------------------------------------------------------------------------------- 1 | This branch is now abandoned, since refactoring was done. This proof-of-concept was rewritten by regular code. 2 | 3 | Please, use [X switcher v1.0 pre-release](https://github.com/ds-voix/xswitcher) . It is now stable enough and fully customizable (all functionality except of "Exec" action). 4 | The precompiled static binary is also placed under "bin/" directory. 5 | -------------------------------------------------------------------------------- /x switcher/bin/xswitcher: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ds-voix/VX-PBX/75e3292dbc21876405258cb60037853137727a83/x switcher/bin/xswitcher -------------------------------------------------------------------------------- /x switcher/draft.txt: -------------------------------------------------------------------------------- 1 | xneur is dead nowadays. The project code looks unusable (for me). 2 | Besides the "fatal flaw", there's at least one problem. It all was the try to build "punto switcher for linux". 3 | But no, PS is erroneous by design! It resides too high in OS abstractions. 4 | PS is buggy enough inside semi-stable m$ windows (e.g. it drops virtualBox buffer integration). 5 | And xNeur brings all the problems to fully unstable linux windows (kde, gnome, …de and so on in the zoo)! 6 | 7 | The problem flows from the idea of "auto-correction". But! I have no need in "auto"! 8 | Quite the contrary, I have the need in "always predictable". 9 | Just to correct the "shit, again I'd started to type with incorrect char table!". 10 | * And m.b. supplementary task: to attach the encoding to windows. 11 | 12 | Try to do it in the obvious (for me) way. 13 | Below is "ToDo" draft. All things must be done at x-org level. 14 | * Not X? Alternatives must have the usable interfaces to do the same things, otherwise they are unusable. 15 | ** Samples uses shell-level enrollments. But the logic must be realized a bit deeper than bash does. 16 | 17 | M.b., play with golang? (c|cpp look as the overkill, python is unstable with its 3.x zoo, perl is ok but only for me) 18 | No, shit # https://github.com/golang/exp/tree/master/shiny/driver/gldriver 19 | 20 | Within python: https://stackoverflow.com/questions/29638210/how-can-i-use-python-xlib-to-generate-a-single-keypress 21 | …The same shit. However, c++ still rules? 22 | 23 | 24 | ### zypper install xdotool xinput xmodmap 25 | 26 | xdotool type "Hello world!" 27 | xdotool key H e l l o space w o r l d exclam x x BackSpace BackSpace 28 | 29 | (sleep 5; xdotool key BackSpace) & 30 | 31 | # xinput list | grep -Po 'id=\K\d+(?=.*slave\s*keyboard)' | xargs -P0 -n1 32 | xinput test 33 | key release 36 34 | key press 65 35 | key release 65 36 | key press 38 37 | key release 38 38 | 39 | xdotool key 22 22 41 42 40 | (sleep 1; xdotool key 37 38 22 22 37 38 39) & 41 | 42 | 43 | ### ToDo ### 44 | 1. Log xinput >> buffer 45 | # xmodmap -pk = show keymap table 46 | ## m.b. used for auto-tune the switcher 47 | # xmodmap -pke = the same, in xmodmap syntax 48 | # xmodmap -pm = show modifiers 49 | ** 65 = space 36 = Return 37 = lCtrl 105 = rCtrl 50 | ** 127 = pause 50 = lShift 62 = rShift 51 | 52 | 2. Drop buffer after specific events 53 | 54 | 3. On "switch" event: 55 | - don't log it to buffer 56 | - send 22 * valid_alphanum_count (drop entered by backspaces) 57 | - do "switch input lanuage" (m.b. through xdotool keydown/keyup) 58 | - drop stored buffer, as it will be sent again 59 | - send buffer again (through xdotool) 60 | 61 | 62 | ### MOUSE keys ### 63 | xinput list | grep -Po 'id=\K\d+(?=.*slave\s*pointer)' | xargs -P0 -n1 64 | xinput test | grep -v motion 65 | 66 | 67 | ### SPECIAL keys ### 68 | - don't log sequences... (e.g. F1..F12) 69 | - don't count (?) sequences... (e.g. accent modifiers) ¡depends on editor 70 | behavior! M.b. =1, m.b. =2 backspaces! 71 | - "compose" sequences, like rAlt_down-4-5-rAlt_up >> ⅘ >> 1 uChar 72 | - drop buffer sequences... (enter, lMouse etc.) 73 | - word separators (space, tab etc.) 74 | - state keys: state must be set at the beginning of replay, released at the 75 | end. 76 | 77 | 78 | ### (?) Get current window (class?/app_name) >> adapt switcher behavior ### 79 | (sleep 5; cat /proc/$(xdotool getwindowpid $(xdotool getwindowfocus))/comm) 80 | (sleep 5; cat /proc/$(xdotool getwindowpid $(xdotool getwindowfocus))/cmdline) 81 | -------------------------------------------------------------------------------- /x switcher/src/C/x11.c: -------------------------------------------------------------------------------- 1 | #include // (libX11-devel | libx11-dev) + (libXmu-devel | libxmu-dev >> WinUtil.h) 2 | #include // Query windows (focus, name, etc.) 3 | #include // Switch window language ("layout") 4 | 5 | Bool xerror = False; 6 | 7 | extern int handle_error(Display* display, XErrorEvent* error) { 8 | xerror = True; 9 | return 1; 10 | } 11 | 12 | void set_handle_error() { 13 | XSetErrorHandler(handle_error); 14 | } 15 | --------------------------------------------------------------------------------